Merge remote-tracking branch 'origin/riscv' into riscv-compliance
commit
1672f9a60d
14
.travis.yml
14
.travis.yml
|
@ -5,6 +5,7 @@ matrix:
|
|||
include:
|
||||
- os: linux
|
||||
env: BUILD=x86_64-linux-gnu
|
||||
compiler: gcc
|
||||
|
||||
- os: linux
|
||||
env: BUILD=i686-linux-gnu CFLAGS=-m32
|
||||
|
@ -12,6 +13,19 @@ matrix:
|
|||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
compiler: gcc
|
||||
|
||||
- os: linux
|
||||
env: BUILD=x86_64-linux-gnu
|
||||
compiler: clang
|
||||
|
||||
- os: linux
|
||||
env: BUILD=i686-linux-gnu CFLAGS=-m32
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
|
||||
script:
|
||||
- ./bootstrap && ./configure && make
|
||||
|
|
|
@ -6,7 +6,7 @@ ARM_OBJCOPY ?= $(ARM_CROSS_COMPILE)objcopy
|
|||
|
||||
ARM_AFLAGS = -EL -mthumb
|
||||
|
||||
arm: armv7m_kinetis_wdog.inc
|
||||
arm: armv7m_kinetis_wdog.inc armv7m_kinetis_wdog32.inc
|
||||
|
||||
armv7m_%.elf: armv7m_%.s
|
||||
$(ARM_AS) $(ARM_AFLAGS) $< -o $@
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* Autogenerated with ../../../src/helper/bin2char.sh */
|
||||
0x04,0x4b,0x05,0x4a,0xda,0x81,0x05,0x4a,0xda,0x81,0x01,0x24,0x1a,0x88,0xa2,0x43,
|
||||
0x1a,0x80,0x06,0xe0,0x00,0x20,0x05,0x40,0x20,0xc5,0x00,0x00,0x28,0xd9,0x00,0x00,
|
||||
0x00,0x00,0x00,0xbe,
|
||||
0x04,0x4a,0xc2,0x81,0x04,0x4a,0xc2,0x81,0x01,0x24,0x02,0x88,0xa2,0x43,0x02,0x80,
|
||||
0x05,0xe0,0x00,0x00,0x20,0xc5,0x00,0x00,0x28,0xd9,0x00,0x00,0x00,0x00,0x00,0xbe,
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
/*
|
||||
Disable watchdog for Kinetis Kx and KVx
|
||||
Parameters: none
|
||||
Parameters:
|
||||
r0 ... WDOG base (in)
|
||||
|
||||
Used instruction set should work on both Cortex-M4 and M0+
|
||||
*/
|
||||
|
||||
|
@ -28,7 +30,6 @@
|
|||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
WDOG_ADDR = 0x40052000
|
||||
/* WDOG registers offsets */
|
||||
WDOG_STCTRLH = 0
|
||||
WDOG_UNLOCK = 0x0e
|
||||
|
@ -39,17 +40,16 @@ WDOG_KEY2 = 0xd928
|
|||
.thumb_func
|
||||
start:
|
||||
/* WDOG_UNLOCK = 0xC520 */
|
||||
ldr r3, =WDOG_ADDR
|
||||
ldr r2, =WDOG_KEY1
|
||||
strh r2, [r3, WDOG_UNLOCK]
|
||||
strh r2, [r0, WDOG_UNLOCK]
|
||||
/* WDOG_UNLOCK = 0xD928 */
|
||||
ldr r2, =WDOG_KEY2
|
||||
strh r2, [r3, WDOG_UNLOCK]
|
||||
strh r2, [r0, WDOG_UNLOCK]
|
||||
/* WDOG_STCTRLH clear bit 0 */
|
||||
movs r4, #1
|
||||
ldrh r2, [r3, WDOG_STCTRLH]
|
||||
ldrh r2, [r0, WDOG_STCTRLH]
|
||||
bics r2, r4
|
||||
strh r2, [r3, WDOG_STCTRLH]
|
||||
strh r2, [r0, WDOG_STCTRLH]
|
||||
/* OpenOCD checks exit point address. Jump to the very end. */
|
||||
b done
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
/* Autogenerated with ../../../src/helper/bin2char.sh */
|
||||
0x02,0x68,0x08,0x4b,0x1a,0x42,0x08,0x4b,0x01,0xd0,0x43,0x60,0x02,0xe0,0x83,0x80,
|
||||
0x1b,0x0c,0x83,0x80,0x80,0x24,0xa2,0x43,0x20,0x24,0x22,0x43,0x02,0x60,0x03,0x4b,
|
||||
0x83,0x60,0x06,0xe0,0x00,0x20,0x00,0x00,0x20,0xc5,0x28,0xd9,0x00,0x04,0x00,0x00,
|
||||
0x00,0x00,0x00,0xbe,
|
|
@ -0,0 +1,81 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2017 Tomas Vanek *
|
||||
* vanekt@fbl.cz *
|
||||
* *
|
||||
* 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, write to the *
|
||||
* Free Software Foundation, Inc. *
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
Disable watchdog, 32-bit version for newer Kinetis
|
||||
Parameters:
|
||||
r0 ... WDOG32 base (in)
|
||||
|
||||
Used instruction set should work on both Cortex-M4 and M0+
|
||||
*/
|
||||
|
||||
.text
|
||||
.syntax unified
|
||||
.cpu cortex-m0
|
||||
.thumb
|
||||
|
||||
/* WDOG registers offsets */
|
||||
WDOG_CS = 0
|
||||
WDOG_CNT = 4
|
||||
WDOG_TOVAL = 8
|
||||
|
||||
WDOG_KEY = 0xd928c520
|
||||
|
||||
.thumb_func
|
||||
start:
|
||||
/* test WDOG_CS bit CMD32EN */
|
||||
ldr r2, [r0, WDOG_CS]
|
||||
ldr r3, =0x2000
|
||||
tst r2, r3
|
||||
ldr r3, =WDOG_KEY
|
||||
beq cmd16
|
||||
|
||||
/* WDOG_CNT = key */
|
||||
str r3, [r0, WDOG_CNT]
|
||||
b unlocked
|
||||
|
||||
cmd16:
|
||||
/* WDOG_CNT = key, halfword by halfword */
|
||||
strh r3, [r0, WDOG_CNT]
|
||||
lsrs r3, r3, #16
|
||||
strh r3, [r0, WDOG_CNT]
|
||||
|
||||
/* WDOG_CS: clear EN bit 7, set UPDATE bit 5 */
|
||||
unlocked:
|
||||
movs r4, #0x80
|
||||
bics r2, r4
|
||||
movs r4, #0x20
|
||||
orrs r2, r4
|
||||
str r2, [r0, WDOG_CS]
|
||||
/* All active WDOG registers have to be updated, set dummy timeout */
|
||||
/* WDOG_TOVAL = 0x400 */
|
||||
ldr r3, =0x400
|
||||
str r3, [r0, WDOG_TOVAL]
|
||||
/* OpenOCD checks exit point address. Jump to the very end. */
|
||||
b done
|
||||
|
||||
.pool
|
||||
|
||||
/* Avoid padding at .text segment end. Otherwise exit point check fails. */
|
||||
.skip ( . - start + 2) & 2, 0
|
||||
done:
|
||||
bkpt #0
|
||||
|
||||
.end
|
||||
|
|
@ -682,7 +682,8 @@ bash$ openocd --help
|
|||
--version | -v display OpenOCD version
|
||||
--file | -f use configuration file <name>
|
||||
--search | -s dir to search for config files and scripts
|
||||
--debug | -d set debug level <0-3>
|
||||
--debug | -d set debug level to 3
|
||||
| -d<n> set debug level to <level>
|
||||
--log_output | -l redirect log output to file <name>
|
||||
--command | -c run <command>
|
||||
@end verbatim
|
||||
|
@ -4715,15 +4716,18 @@ each block, and the specified length must stay within that bank.
|
|||
@end deffn
|
||||
@comment no current checks for errors if fill blocks touch multiple banks!
|
||||
|
||||
@deffn Command {flash write_bank} num filename offset
|
||||
@deffn Command {flash write_bank} num filename [offset]
|
||||
Write the binary @file{filename} to flash bank @var{num},
|
||||
starting at @var{offset} bytes from the beginning of the bank.
|
||||
starting at @var{offset} bytes from the beginning of the bank. If @var{offset}
|
||||
is omitted, start at the beginning of the flash bank.
|
||||
The @var{num} parameter is a value shown by @command{flash banks}.
|
||||
@end deffn
|
||||
|
||||
@deffn Command {flash read_bank} num filename offset length
|
||||
@deffn Command {flash read_bank} num filename [offset [length]]
|
||||
Read @var{length} bytes from the flash bank @var{num} starting at @var{offset}
|
||||
and write the contents to the binary @file{filename}.
|
||||
and write the contents to the binary @file{filename}. If @var{offset} is
|
||||
omitted, start at the beginning of the flash bank. If @var{length} is omitted,
|
||||
read the remaining bytes from the flash bank.
|
||||
The @var{num} parameter is a value shown by @command{flash banks}.
|
||||
@end deffn
|
||||
|
||||
|
@ -5361,15 +5365,28 @@ nor is Chip Erase (only Sector Erase is implemented).}
|
|||
|
||||
@deffn {Flash Driver} kinetis
|
||||
@cindex kinetis
|
||||
Kx and KLx members of the Kinetis microcontroller family from Freescale include
|
||||
Kx, KLx, KVx and KE1x members of the Kinetis microcontroller family
|
||||
from NXP (former Freescale) include
|
||||
internal flash and use ARM Cortex-M0+ or M4 cores. The driver automatically
|
||||
recognizes flash size and a number of flash banks (1-4) using the chip
|
||||
identification register, and autoconfigures itself.
|
||||
Use kinetis_ke driver for KE0x devices.
|
||||
|
||||
The @var{kinetis} driver defines option:
|
||||
@itemize
|
||||
@item -sim-base @var{addr} ... base of System Integration Module where chip identification resides. Driver tries two known locations if option is omitted.
|
||||
@end itemize
|
||||
|
||||
@example
|
||||
flash bank $_FLASHNAME kinetis 0 0 0 0 $_TARGETNAME
|
||||
@end example
|
||||
|
||||
@deffn Command {kinetis create_banks}
|
||||
Configuration command enables automatic creation of additional flash banks
|
||||
based on real flash layout of device. Banks are created during device probe.
|
||||
Use 'flash probe 0' to force probe.
|
||||
@end deffn
|
||||
|
||||
@deffn Command {kinetis fcf_source} [protection|write]
|
||||
Select what source is used when writing to a Flash Configuration Field.
|
||||
@option{protection} mode builds FCF content from protection bits previously
|
||||
|
@ -5446,10 +5463,11 @@ Command disables watchdog timer.
|
|||
|
||||
@deffn {Flash Driver} kinetis_ke
|
||||
@cindex kinetis_ke
|
||||
KE members of the Kinetis microcontroller family from Freescale include
|
||||
KE0x members of the Kinetis microcontroller family from Freescale include
|
||||
internal flash and use ARM Cortex-M0+. The driver automatically recognizes
|
||||
the KE family and sub-family using the chip identification register, and
|
||||
the KE0x sub-family using the chip identification register, and
|
||||
autoconfigures itself.
|
||||
Use kinetis (not kinetis_ke) driver for KE1x devices.
|
||||
|
||||
@example
|
||||
flash bank $_FLASHNAME kinetis_ke 0 0 0 0 $_TARGETNAME
|
||||
|
@ -5971,16 +5989,21 @@ The @var{num} parameter is a value shown by @command{flash banks}.
|
|||
@end deffn
|
||||
|
||||
@deffn Command {stm32f2x options_read} num
|
||||
Reads and displays user options and (where implemented) boot_addr0 and boot_addr1.
|
||||
Reads and displays user options and (where implemented) boot_addr0, boot_addr1, optcr2.
|
||||
The @var{num} parameter is a value shown by @command{flash banks}.
|
||||
@end deffn
|
||||
|
||||
@deffn Command {stm32f2x options_write} num user_options boot_addr0 boot_addr1
|
||||
Writes user options and (where implemented) boot_addr0 and boot_addr1 in raw format.
|
||||
Warning: The meaning of the various bits depends on the device, always check datasheet!
|
||||
The @var{num} parameter is a value shown by @command{flash banks}, user_options a
|
||||
12 bit value, consisting of bits 31-28 and 7-0 of FLASH_OPTCR, boot_addr0 and boot_addr1
|
||||
two halfwords (of FLASH_OPTCR1).
|
||||
The @var{num} parameter is a value shown by @command{flash banks}, @var{user_options} a
|
||||
12 bit value, consisting of bits 31-28 and 7-0 of FLASH_OPTCR, @var{boot_addr0} and
|
||||
@var{boot_addr1} two halfwords (of FLASH_OPTCR1).
|
||||
@end deffn
|
||||
|
||||
@deffn Command {stm32f2x optcr2_write} num optcr2
|
||||
Writes FLASH_OPTCR2 options. Warning: Clearing PCROPi bits requires a full mass erase!
|
||||
The @var{num} parameter is a value shown by @command{flash banks}, @var{optcr2} a 32-bit word.
|
||||
@end deffn
|
||||
@end deffn
|
||||
|
||||
|
@ -6832,12 +6855,13 @@ non-zero exit code to the parent process.
|
|||
@deffn Command debug_level [n]
|
||||
@cindex message level
|
||||
Display debug level.
|
||||
If @var{n} (from 0..3) is provided, then set it to that level.
|
||||
If @var{n} (from 0..4) is provided, then set it to that level.
|
||||
This affects the kind of messages sent to the server log.
|
||||
Level 0 is error messages only;
|
||||
level 1 adds warnings;
|
||||
level 2 adds informational messages;
|
||||
and level 3 adds debugging messages.
|
||||
level 3 adds debugging messages;
|
||||
and level 4 adds verbose low-level debug messages.
|
||||
The default is level 2, but that can be overridden on
|
||||
the command line along with the location of that log
|
||||
file (which is normally the server's standard output).
|
||||
|
|
|
@ -22,8 +22,8 @@ NOR_DRIVERS = \
|
|||
%D%/dsp5680xx_flash.c \
|
||||
%D%/efm32.c \
|
||||
%D%/em357.c \
|
||||
%D%/fespi.c \
|
||||
%D%/faux.c \
|
||||
%D%/fespi.c \
|
||||
%D%/fm3.c \
|
||||
%D%/fm4.c \
|
||||
%D%/jtagspi.c \
|
||||
|
|
|
@ -464,7 +464,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 1024 * 1024,
|
||||
.nsectors = 128,
|
||||
|
@ -499,7 +499,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
@ -532,7 +532,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
@ -565,7 +565,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
@ -598,7 +598,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 1024 * 1024,
|
||||
.nsectors = 128,
|
||||
|
@ -631,7 +631,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 1024 * 1024,
|
||||
.nsectors = 128,
|
||||
|
@ -1279,7 +1279,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK0_BASE_SD,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
@ -1295,7 +1295,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 1,
|
||||
.base_address = FLASH_BANK1_BASE_1024K_SD,
|
||||
.controller_address = 0x400e0c00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
@ -1305,10 +1305,10 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
},
|
||||
},
|
||||
|
||||
/* at91samg53n19 */
|
||||
/* atsamg53n19 */
|
||||
{
|
||||
.chipid_cidr = 0x247e0ae0,
|
||||
.name = "at91samg53n19",
|
||||
.name = "atsamg53n19",
|
||||
.total_flash_size = 512 * 1024,
|
||||
.total_sram_size = 96 * 1024,
|
||||
.n_gpnvms = 2,
|
||||
|
@ -1323,7 +1323,7 @@ static const struct sam4_chip_details all_sam4_details[] = {
|
|||
.bank_number = 0,
|
||||
.base_address = FLASH_BANK_BASE_S,
|
||||
.controller_address = 0x400e0a00,
|
||||
.flash_wait_states = 6, /* workaround silicon bug */
|
||||
.flash_wait_states = 5,
|
||||
.present = 1,
|
||||
.size_bytes = 512 * 1024,
|
||||
.nsectors = 64,
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
#include <target/cortex_m.h>
|
||||
|
||||
#define SAMD_NUM_SECTORS 16
|
||||
#define SAMD_NUM_PROT_BLOCKS 16
|
||||
#define SAMD_PAGE_SIZE_MAX 1024
|
||||
|
||||
#define SAMD_FLASH ((uint32_t)0x00000000) /* physical Flash memory */
|
||||
|
@ -286,6 +286,7 @@ struct samd_info {
|
|||
uint32_t page_size;
|
||||
int num_pages;
|
||||
int sector_size;
|
||||
int prot_block_size;
|
||||
|
||||
bool probed;
|
||||
struct target *target;
|
||||
|
@ -319,7 +320,7 @@ static const struct samd_part *samd_find_part(uint32_t id)
|
|||
|
||||
static int samd_protect_check(struct flash_bank *bank)
|
||||
{
|
||||
int res;
|
||||
int res, prot_block;
|
||||
uint16_t lock;
|
||||
|
||||
res = target_read_u16(bank->target,
|
||||
|
@ -328,8 +329,8 @@ static int samd_protect_check(struct flash_bank *bank)
|
|||
return res;
|
||||
|
||||
/* Lock bits are active-low */
|
||||
for (int i = 0; i < bank->num_sectors; i++)
|
||||
bank->sectors[i].is_protected = !(lock & (1<<i));
|
||||
for (prot_block = 0; prot_block < bank->num_prot_blocks; prot_block++)
|
||||
bank->prot_blocks[prot_block].is_protected = !(lock & (1u<<prot_block));
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
@ -380,8 +381,6 @@ static int samd_probe(struct flash_bank *bank)
|
|||
|
||||
bank->size = part->flash_kb * 1024;
|
||||
|
||||
chip->sector_size = bank->size / SAMD_NUM_SECTORS;
|
||||
|
||||
res = samd_get_flash_page_info(bank->target, &chip->page_size,
|
||||
&chip->num_pages);
|
||||
if (res != ERROR_OK) {
|
||||
|
@ -397,21 +396,23 @@ static int samd_probe(struct flash_bank *bank)
|
|||
part->flash_kb, chip->num_pages, chip->page_size);
|
||||
}
|
||||
|
||||
/* Erase granularity = 1 row = 4 pages */
|
||||
chip->sector_size = chip->page_size * 4;
|
||||
|
||||
/* Allocate the sector table */
|
||||
bank->num_sectors = SAMD_NUM_SECTORS;
|
||||
bank->sectors = calloc(bank->num_sectors, sizeof((bank->sectors)[0]));
|
||||
bank->num_sectors = chip->num_pages / 4;
|
||||
bank->sectors = alloc_block_array(0, chip->sector_size, bank->num_sectors);
|
||||
if (!bank->sectors)
|
||||
return ERROR_FAIL;
|
||||
|
||||
/* Fill out the sector information: all SAMD sectors are the same size and
|
||||
* there is always a fixed number of them. */
|
||||
for (int i = 0; i < bank->num_sectors; i++) {
|
||||
bank->sectors[i].size = chip->sector_size;
|
||||
bank->sectors[i].offset = i * chip->sector_size;
|
||||
/* mark as unknown */
|
||||
bank->sectors[i].is_erased = -1;
|
||||
bank->sectors[i].is_protected = -1;
|
||||
}
|
||||
/* 16 protection blocks per device */
|
||||
chip->prot_block_size = bank->size / SAMD_NUM_PROT_BLOCKS;
|
||||
|
||||
/* Allocate the table of protection blocks */
|
||||
bank->num_prot_blocks = SAMD_NUM_PROT_BLOCKS;
|
||||
bank->prot_blocks = alloc_block_array(0, chip->prot_block_size, bank->num_prot_blocks);
|
||||
if (!bank->prot_blocks)
|
||||
return ERROR_FAIL;
|
||||
|
||||
samd_protect_check(bank);
|
||||
|
||||
|
@ -606,9 +607,10 @@ out_user_row:
|
|||
return res;
|
||||
}
|
||||
|
||||
static int samd_protect(struct flash_bank *bank, int set, int first, int last)
|
||||
static int samd_protect(struct flash_bank *bank, int set, int first_prot_bl, int last_prot_bl)
|
||||
{
|
||||
struct samd_info *chip = (struct samd_info *)bank->driver_priv;
|
||||
int res = ERROR_OK;
|
||||
int prot_block;
|
||||
|
||||
/* We can issue lock/unlock region commands with the target running but
|
||||
* the settings won't persist unless we're able to modify the LOCK regions
|
||||
|
@ -618,18 +620,16 @@ static int samd_protect(struct flash_bank *bank, int set, int first, int last)
|
|||
return ERROR_TARGET_NOT_HALTED;
|
||||
}
|
||||
|
||||
int res = ERROR_OK;
|
||||
|
||||
for (int s = first; s <= last; s++) {
|
||||
if (set != bank->sectors[s].is_protected) {
|
||||
/* Load an address that is within this sector (we use offset 0) */
|
||||
for (prot_block = first_prot_bl; prot_block <= last_prot_bl; prot_block++) {
|
||||
if (set != bank->prot_blocks[prot_block].is_protected) {
|
||||
/* Load an address that is within this protection block (we use offset 0) */
|
||||
res = target_write_u32(bank->target,
|
||||
SAMD_NVMCTRL + SAMD_NVMCTRL_ADDR,
|
||||
((s * chip->sector_size) >> 1));
|
||||
bank->prot_blocks[prot_block].offset >> 1);
|
||||
if (res != ERROR_OK)
|
||||
goto exit;
|
||||
|
||||
/* Tell the controller to lock that sector */
|
||||
/* Tell the controller to lock that block */
|
||||
res = samd_issue_nvmctrl_command(bank->target,
|
||||
set ? SAMD_NVM_CMD_LR : SAMD_NVM_CMD_UR);
|
||||
if (res != ERROR_OK)
|
||||
|
@ -644,7 +644,7 @@ static int samd_protect(struct flash_bank *bank, int set, int first, int last)
|
|||
* locked. See Table 9-3 in the SAMD20 datasheet for more details. */
|
||||
|
||||
res = samd_modify_user_row(bank->target, set ? 0x0000 : 0xFFFF,
|
||||
48 + first, 48 + last);
|
||||
48 + first_prot_bl, 48 + last_prot_bl);
|
||||
if (res != ERROR_OK)
|
||||
LOG_WARNING("SAMD: protect settings were not made persistent!");
|
||||
|
||||
|
@ -656,10 +656,9 @@ exit:
|
|||
return res;
|
||||
}
|
||||
|
||||
static int samd_erase(struct flash_bank *bank, int first, int last)
|
||||
static int samd_erase(struct flash_bank *bank, int first_sect, int last_sect)
|
||||
{
|
||||
int res;
|
||||
int rows_in_sector;
|
||||
int res, s;
|
||||
struct samd_info *chip = (struct samd_info *)bank->driver_priv;
|
||||
|
||||
if (bank->target->state != TARGET_HALTED) {
|
||||
|
@ -673,26 +672,12 @@ static int samd_erase(struct flash_bank *bank, int first, int last)
|
|||
return ERROR_FLASH_BANK_NOT_PROBED;
|
||||
}
|
||||
|
||||
/* The SAMD NVM has row erase granularity. There are four pages in a row
|
||||
* and the number of rows in a sector depends on the sector size, which in
|
||||
* turn depends on the Flash capacity as there is a fixed number of
|
||||
* sectors. */
|
||||
rows_in_sector = chip->sector_size / (chip->page_size * 4);
|
||||
|
||||
/* For each sector to be erased */
|
||||
for (int s = first; s <= last; s++) {
|
||||
if (bank->sectors[s].is_protected) {
|
||||
LOG_ERROR("SAMD: failed to erase sector %d. That sector is write-protected", s);
|
||||
return ERROR_FLASH_OPERATION_FAILED;
|
||||
}
|
||||
|
||||
/* For each row in that sector */
|
||||
for (int r = s * rows_in_sector; r < (s + 1) * rows_in_sector; r++) {
|
||||
res = samd_erase_row(bank->target, r * chip->page_size * 4);
|
||||
if (res != ERROR_OK) {
|
||||
LOG_ERROR("SAMD: failed to erase sector %d", s);
|
||||
return res;
|
||||
}
|
||||
for (s = first_sect; s <= last_sect; s++) {
|
||||
res = samd_erase_row(bank->target, bank->sectors[s].offset);
|
||||
if (res != ERROR_OK) {
|
||||
LOG_ERROR("SAMD: failed to erase sector %d at 0x%08" PRIx32, s, bank->sectors[s].offset);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#define EFM_FAMILY_ID_HAPPY_GECKO 77
|
||||
#define EZR_FAMILY_ID_WONDER_GECKO 120
|
||||
#define EZR_FAMILY_ID_LEOPARD_GECKO 121
|
||||
#define EZR_FAMILY_ID_HAPPY_GECKO 122
|
||||
|
||||
#define EFM32_FLASH_ERASE_TMO 100
|
||||
#define EFM32_FLASH_WDATAREADY_TMO 100
|
||||
|
@ -178,7 +179,8 @@ static int efm32x_read_info(struct flash_bank *bank,
|
|||
EFM_FAMILY_ID_TINY_GECKO == efm32_info->part_family)
|
||||
efm32_info->page_size = 512;
|
||||
else if (EFM_FAMILY_ID_ZERO_GECKO == efm32_info->part_family ||
|
||||
EFM_FAMILY_ID_HAPPY_GECKO == efm32_info->part_family)
|
||||
EFM_FAMILY_ID_HAPPY_GECKO == efm32_info->part_family ||
|
||||
EZR_FAMILY_ID_HAPPY_GECKO == efm32_info->part_family)
|
||||
efm32_info->page_size = 1024;
|
||||
else if (EFM_FAMILY_ID_GIANT_GECKO == efm32_info->part_family ||
|
||||
EFM_FAMILY_ID_LEOPARD_GECKO == efm32_info->part_family) {
|
||||
|
@ -236,6 +238,7 @@ static int efm32x_decode_info(struct efm32_info *info, char *buf, int buf_size)
|
|||
switch (info->part_family) {
|
||||
case EZR_FAMILY_ID_WONDER_GECKO:
|
||||
case EZR_FAMILY_ID_LEOPARD_GECKO:
|
||||
case EZR_FAMILY_ID_HAPPY_GECKO:
|
||||
printed = snprintf(buf, buf_size, "EZR32 ");
|
||||
break;
|
||||
default:
|
||||
|
@ -270,6 +273,7 @@ static int efm32x_decode_info(struct efm32_info *info, char *buf, int buf_size)
|
|||
printed = snprintf(buf, buf_size, "Zero Gecko");
|
||||
break;
|
||||
case EFM_FAMILY_ID_HAPPY_GECKO:
|
||||
case EZR_FAMILY_ID_HAPPY_GECKO:
|
||||
printed = snprintf(buf, buf_size, "Happy Gecko");
|
||||
break;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -59,10 +59,14 @@
|
|||
*
|
||||
* Sector sizes in kiBytes:
|
||||
* 1 MiByte part with 4 x 16, 1 x 64, 7 x 128.
|
||||
* 1.5 MiByte part with 4 x 16, 1 x 64, 11 x 128.
|
||||
* 2 MiByte part with 4 x 16, 1 x 64, 7 x 128, 4 x 16, 1 x 64, 7 x 128.
|
||||
* 1 MiByte STM32F42x/43x part with DB1M Option set:
|
||||
* 4 x 16, 1 x 64, 3 x 128, 4 x 16, 1 x 64, 3 x 128.
|
||||
*
|
||||
* STM32F7[2|3]
|
||||
* 512 kiByte part with 4 x 16, 1 x 64, 3 x 128.
|
||||
*
|
||||
* STM32F7[4|5]
|
||||
* 1 MiByte part with 4 x 32, 1 x 128, 3 x 256.
|
||||
*
|
||||
|
@ -93,6 +97,12 @@
|
|||
* RM0410
|
||||
* http://www.st.com/resource/en/reference_manual/dm00224583.pdf
|
||||
*
|
||||
* RM0430
|
||||
* http://www.st.com/resource/en/reference_manual/dm00305666.pdf
|
||||
*
|
||||
* RM0431
|
||||
* http://www.st.com/resource/en/reference_manual/dm00305990.pdf
|
||||
*
|
||||
* STM32F1x series - notice that this code was copy, pasted and knocked
|
||||
* into a stm32f2x driver, so in case something has been converted or
|
||||
* bugs haven't been fixed, here are the original manuals:
|
||||
|
@ -121,6 +131,7 @@
|
|||
#define STM32_FLASH_CR 0x40023c10
|
||||
#define STM32_FLASH_OPTCR 0x40023c14
|
||||
#define STM32_FLASH_OPTCR1 0x40023c18
|
||||
#define STM32_FLASH_OPTCR2 0x40023c1c
|
||||
|
||||
/* FLASH_CR register bits */
|
||||
#define FLASH_PG (1 << 0)
|
||||
|
@ -152,6 +163,10 @@
|
|||
#define OPTCR_START (1 << 1)
|
||||
#define OPTCR_NDBANK (1 << 29) /* not dual bank mode */
|
||||
#define OPTCR_DB1M (1 << 30) /* 1 MiB devices dual flash bank option */
|
||||
#define OPTCR_SPRMOD (1 << 31) /* switches PCROPi/nWPRi interpretation */
|
||||
|
||||
/* STM32_FLASH_OPTCR2 register bits */
|
||||
#define OPTCR2_PCROP_RDP (1 << 31) /* erase PCROP zone when decreasing RDP */
|
||||
|
||||
/* register unlock keys */
|
||||
#define KEY1 0x45670123
|
||||
|
@ -166,14 +181,17 @@ struct stm32x_options {
|
|||
uint16_t user_options; /* bit 0-7 usual options, bit 8-11 extra options */
|
||||
uint32_t protection;
|
||||
uint32_t boot_addr;
|
||||
uint32_t optcr2_pcrop;
|
||||
};
|
||||
|
||||
struct stm32x_flash_bank {
|
||||
struct stm32x_options option_bytes;
|
||||
int probed;
|
||||
bool has_large_mem; /* F42x/43x/469/479/7xx in dual bank mode */
|
||||
bool has_boot_addr; /* F7xx */
|
||||
bool has_extra_options; /* F42x/43x/469/479/7xx */
|
||||
bool has_boot_addr; /* F7xx */
|
||||
bool has_optcr2_pcrop; /* F72x/73x */
|
||||
int protection_bits; /* F413/423 */
|
||||
uint32_t user_bank_size;
|
||||
};
|
||||
|
||||
|
@ -328,11 +346,13 @@ static int stm32x_read_options(struct flash_bank *bank)
|
|||
* whereas F7 6 bits (IWDG_SW and WWDG_SW) in user_options */
|
||||
stm32x_info->option_bytes.user_options = optiondata & 0xfc;
|
||||
stm32x_info->option_bytes.RDP = (optiondata >> 8) & 0xff;
|
||||
stm32x_info->option_bytes.protection = (optiondata >> 16) & 0xfff;
|
||||
stm32x_info->option_bytes.protection =
|
||||
(optiondata >> 16) & (~(0xffff << stm32x_info->protection_bits) & 0xffff);
|
||||
|
||||
if (stm32x_info->has_extra_options) {
|
||||
/* F42x/43x/469/479 and 7xx have up to 4 bits of extra options */
|
||||
stm32x_info->option_bytes.user_options |= (optiondata >> 20) & 0xf00;
|
||||
stm32x_info->option_bytes.user_options |= (optiondata >> 20) &
|
||||
((0xf00 << (stm32x_info->protection_bits - 12)) & 0xf00);
|
||||
}
|
||||
|
||||
if (stm32x_info->has_large_mem || stm32x_info->has_boot_addr) {
|
||||
|
@ -350,6 +370,20 @@ static int stm32x_read_options(struct flash_bank *bank)
|
|||
}
|
||||
}
|
||||
|
||||
if (stm32x_info->has_optcr2_pcrop) {
|
||||
retval = target_read_u32(target, STM32_FLASH_OPTCR2, &optiondata);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
|
||||
stm32x_info->option_bytes.optcr2_pcrop = optiondata;
|
||||
if (stm32x_info->has_optcr2_pcrop &&
|
||||
(stm32x_info->option_bytes.optcr2_pcrop & ~OPTCR2_PCROP_RDP)) {
|
||||
LOG_INFO("PCROP Engaged");
|
||||
}
|
||||
} else {
|
||||
stm32x_info->option_bytes.optcr2_pcrop = 0x0;
|
||||
}
|
||||
|
||||
if (stm32x_info->option_bytes.RDP != 0xAA)
|
||||
LOG_INFO("Device Security Bit Set");
|
||||
|
||||
|
@ -371,11 +405,13 @@ static int stm32x_write_options(struct flash_bank *bank)
|
|||
/* rebuild option data */
|
||||
optiondata = stm32x_info->option_bytes.user_options & 0xfc;
|
||||
optiondata |= stm32x_info->option_bytes.RDP << 8;
|
||||
optiondata |= (stm32x_info->option_bytes.protection & 0x0fff) << 16;
|
||||
optiondata |= (stm32x_info->option_bytes.protection &
|
||||
(~(0xffff << stm32x_info->protection_bits))) << 16;
|
||||
|
||||
if (stm32x_info->has_extra_options) {
|
||||
/* F42x/43x/469/479 and 7xx have up to 4 bits of extra options */
|
||||
optiondata |= (stm32x_info->option_bytes.user_options & 0xf00) << 20;
|
||||
optiondata |= (stm32x_info->option_bytes.user_options &
|
||||
((0xf00 << (stm32x_info->protection_bits - 12)) & 0xf00)) << 20;
|
||||
}
|
||||
|
||||
if (stm32x_info->has_large_mem || stm32x_info->has_boot_addr) {
|
||||
|
@ -392,6 +428,14 @@ static int stm32x_write_options(struct flash_bank *bank)
|
|||
return retval;
|
||||
}
|
||||
|
||||
/* program extra pcrop register */
|
||||
if (stm32x_info->has_optcr2_pcrop) {
|
||||
retval = target_write_u32(target, STM32_FLASH_OPTCR2,
|
||||
stm32x_info->option_bytes.optcr2_pcrop);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* program options */
|
||||
retval = target_write_u32(target, STM32_FLASH_OPTCR, optiondata);
|
||||
if (retval != ERROR_OK)
|
||||
|
@ -418,6 +462,8 @@ static int stm32x_write_options(struct flash_bank *bank)
|
|||
static int stm32x_protect_check(struct flash_bank *bank)
|
||||
{
|
||||
struct stm32x_flash_bank *stm32x_info = bank->driver_priv;
|
||||
struct flash_sector *prot_blocks;
|
||||
int num_prot_blocks;
|
||||
|
||||
/* read write protection settings */
|
||||
int retval = stm32x_read_options(bank);
|
||||
|
@ -426,27 +472,18 @@ static int stm32x_protect_check(struct flash_bank *bank)
|
|||
return retval;
|
||||
}
|
||||
|
||||
if (stm32x_info->has_boot_addr && stm32x_info->has_large_mem) {
|
||||
/* F76x/77x: bit k protects sectors 2*k and 2*k+1 */
|
||||
for (int i = 0; i < (bank->num_sectors >> 1); i++) {
|
||||
if (stm32x_info->option_bytes.protection & (1 << i)) {
|
||||
bank->sectors[i << 1].is_protected = 0;
|
||||
bank->sectors[(i << 1) + 1].is_protected = 0;
|
||||
} else {
|
||||
bank->sectors[i << 1].is_protected = 1;
|
||||
bank->sectors[(i << 1) + 1].is_protected = 1;
|
||||
}
|
||||
}
|
||||
if (bank->prot_blocks) {
|
||||
num_prot_blocks = bank->num_prot_blocks;
|
||||
prot_blocks = bank->prot_blocks;
|
||||
} else {
|
||||
/* one protection bit per sector */
|
||||
for (int i = 0; i < bank->num_sectors; i++) {
|
||||
if (stm32x_info->option_bytes.protection & (1 << i))
|
||||
bank->sectors[i].is_protected = 0;
|
||||
else
|
||||
bank->sectors[i].is_protected = 1;
|
||||
}
|
||||
num_prot_blocks = bank->num_sectors;
|
||||
prot_blocks = bank->sectors;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_prot_blocks; i++)
|
||||
prot_blocks[i].is_protected =
|
||||
~(stm32x_info->option_bytes.protection >> i) & 1;
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
|
@ -515,17 +552,6 @@ static int stm32x_protect(struct flash_bank *bank, int set, int first, int last)
|
|||
return retval;
|
||||
}
|
||||
|
||||
if (stm32x_info->has_boot_addr && stm32x_info->has_large_mem) {
|
||||
/* F76x/77x: bit k protects sectors 2*k and 2*k+1 */
|
||||
if ((first & 1) != 0 || (last & 1) != 1) {
|
||||
LOG_ERROR("sector protection must be double sector aligned");
|
||||
return ERROR_FAIL;
|
||||
} else {
|
||||
first >>= 1;
|
||||
last >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = first; i <= last; i++) {
|
||||
if (set)
|
||||
stm32x_info->option_bytes.protection &= ~(1 << i);
|
||||
|
@ -829,7 +855,7 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
{
|
||||
struct target *target = bank->target;
|
||||
struct stm32x_flash_bank *stm32x_info = bank->driver_priv;
|
||||
int i;
|
||||
int i, num_prot_blocks;
|
||||
uint16_t flash_size_in_kb;
|
||||
uint32_t flash_size_reg = 0x1FFF7A22;
|
||||
uint16_t max_sector_size_in_kb = 128;
|
||||
|
@ -841,15 +867,31 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
stm32x_info->has_large_mem = false;
|
||||
stm32x_info->has_boot_addr = false;
|
||||
stm32x_info->has_extra_options = false;
|
||||
stm32x_info->has_optcr2_pcrop = false;
|
||||
stm32x_info->protection_bits = 12; /* max. number of nWRPi bits (in FLASH_OPTCR !!!) */
|
||||
num_prot_blocks = 0;
|
||||
|
||||
if (bank->sectors) {
|
||||
free(bank->sectors);
|
||||
bank->num_sectors = 0;
|
||||
bank->sectors = NULL;
|
||||
}
|
||||
|
||||
if (bank->prot_blocks) {
|
||||
free(bank->prot_blocks);
|
||||
bank->num_prot_blocks = 0;
|
||||
bank->prot_blocks = NULL;
|
||||
}
|
||||
|
||||
/* read stm32 device id register */
|
||||
int retval = stm32x_get_device_id(bank, &device_id);
|
||||
if (retval != ERROR_OK)
|
||||
return retval;
|
||||
LOG_INFO("device id = 0x%08" PRIx32 "", device_id);
|
||||
device_id &= 0xfff; /* only bits 0-11 are used further on */
|
||||
|
||||
/* set max flash size depending on family, id taken from AN2606 */
|
||||
switch (device_id & 0xfff) {
|
||||
switch (device_id) {
|
||||
case 0x411: /* F20x/21x */
|
||||
case 0x413: /* F40x/41x */
|
||||
max_flash_size_in_kb = 1024;
|
||||
|
@ -892,6 +934,21 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
stm32x_info->has_boot_addr = true;
|
||||
break;
|
||||
|
||||
case 0x452: /* F72x/73x */
|
||||
max_flash_size_in_kb = 512;
|
||||
flash_size_reg = 0x1FF07A22; /* yes, 0x1FF*0*7A22, not 0x1FF*F*7A22 */
|
||||
stm32x_info->has_extra_options = true;
|
||||
stm32x_info->has_boot_addr = true;
|
||||
stm32x_info->has_optcr2_pcrop = true;
|
||||
break;
|
||||
|
||||
case 0x463: /* F413x/423x */
|
||||
max_flash_size_in_kb = 1536;
|
||||
stm32x_info->has_extra_options = true;
|
||||
stm32x_info->protection_bits = 15;
|
||||
num_prot_blocks = 15;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_WARNING("Cannot identify target as a STM32 family.");
|
||||
return ERROR_FAIL;
|
||||
|
@ -920,12 +977,8 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
/* did we assign flash size? */
|
||||
assert(flash_size_in_kb != 0xffff);
|
||||
|
||||
/* Devices with > 1024 kiByte always are dual-banked */
|
||||
if (flash_size_in_kb > 1024)
|
||||
stm32x_info->has_large_mem = true;
|
||||
|
||||
/* F42x/43x/469/479 1024 kiByte devices have a dual bank option */
|
||||
if ((device_id & 0xfff) == 0x419 || (device_id & 0xfff) == 0x434) {
|
||||
if ((device_id == 0x419) || (device_id == 0x434)) {
|
||||
uint32_t optiondata;
|
||||
retval = target_read_u32(target, STM32_FLASH_OPTCR, &optiondata);
|
||||
if (retval != ERROR_OK) {
|
||||
|
@ -942,7 +995,7 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
}
|
||||
|
||||
/* F76x/77x devices have a dual bank option */
|
||||
if ((device_id & 0xfff) == 0x451) {
|
||||
if (device_id == 0x451) {
|
||||
uint32_t optiondata;
|
||||
retval = target_read_u32(target, STM32_FLASH_OPTCR, &optiondata);
|
||||
if (retval != ERROR_OK) {
|
||||
|
@ -963,11 +1016,6 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
int num_pages = flash_size_in_kb / max_sector_size_in_kb
|
||||
+ (stm32x_info->has_large_mem ? 8 : 4);
|
||||
|
||||
if (bank->sectors) {
|
||||
free(bank->sectors);
|
||||
bank->sectors = NULL;
|
||||
}
|
||||
|
||||
bank->base = base_address;
|
||||
bank->num_sectors = num_pages;
|
||||
bank->sectors = malloc(sizeof(struct flash_sector) * num_pages);
|
||||
|
@ -978,15 +1026,44 @@ static int stm32x_probe(struct flash_bank *bank)
|
|||
bank->size = 0;
|
||||
LOG_DEBUG("allocated %d sectors", num_pages);
|
||||
|
||||
/* F76x/77x in dual bank mode */
|
||||
if ((device_id == 0x451) && stm32x_info->has_large_mem)
|
||||
num_prot_blocks = num_pages >> 1;
|
||||
|
||||
if (num_prot_blocks) {
|
||||
bank->prot_blocks = malloc(sizeof(struct flash_sector) * num_prot_blocks);
|
||||
for (i = 0; i < num_prot_blocks; i++)
|
||||
bank->prot_blocks[i].is_protected = 0;
|
||||
LOG_DEBUG("allocated %d prot blocks", num_prot_blocks);
|
||||
}
|
||||
|
||||
if (stm32x_info->has_large_mem) {
|
||||
/* dual-bank */
|
||||
setup_bank(bank, 0, flash_size_in_kb >> 1, max_sector_size_in_kb);
|
||||
setup_bank(bank, num_pages >> 1, flash_size_in_kb >> 1,
|
||||
max_sector_size_in_kb);
|
||||
|
||||
/* F767x/F77x in dual mode, one protection bit refers to two adjacent sectors */
|
||||
if (device_id == 0x451) {
|
||||
for (i = 0; i < num_prot_blocks; i++) {
|
||||
bank->prot_blocks[i].offset = bank->sectors[i << 1].offset;
|
||||
bank->prot_blocks[i].size = bank->sectors[i << 1].size << 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* single-bank */
|
||||
setup_bank(bank, 0, flash_size_in_kb, max_sector_size_in_kb);
|
||||
|
||||
/* F413/F423, sectors 14 and 15 share one common protection bit */
|
||||
if (device_id == 0x463) {
|
||||
for (i = 0; i < num_prot_blocks; i++) {
|
||||
bank->prot_blocks[i].offset = bank->sectors[i].offset;
|
||||
bank->prot_blocks[i].size = bank->sectors[i].size;
|
||||
}
|
||||
bank->prot_blocks[num_prot_blocks - 1].size <<= 1;
|
||||
}
|
||||
}
|
||||
bank->num_prot_blocks = num_prot_blocks;
|
||||
assert((bank->size >> 10) == flash_size_in_kb);
|
||||
|
||||
stm32x_info->probed = 1;
|
||||
|
@ -1107,6 +1184,14 @@ static int get_stm32x_info(struct flash_bank *bank, char *buf, int buf_size)
|
|||
case 0x1001:
|
||||
rev_str = "Z";
|
||||
break;
|
||||
|
||||
case 0x2000:
|
||||
rev_str = "B";
|
||||
break;
|
||||
|
||||
case 0x3000:
|
||||
rev_str = "C";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1134,6 +1219,26 @@ static int get_stm32x_info(struct flash_bank *bank, char *buf, int buf_size)
|
|||
}
|
||||
break;
|
||||
|
||||
case 0x452:
|
||||
device_str = "STM32F7[2|3]x";
|
||||
|
||||
switch (rev_id) {
|
||||
case 0x1000:
|
||||
rev_str = "A";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x463:
|
||||
device_str = "STM32F4[1|2]3";
|
||||
|
||||
switch (rev_id) {
|
||||
case 0x1000:
|
||||
rev_str = "A";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
snprintf(buf, buf_size, "Cannot identify target as a STM32F2/4/7\n");
|
||||
return ERROR_FAIL;
|
||||
|
@ -1164,8 +1269,8 @@ COMMAND_HANDLER(stm32x_handle_lock_command)
|
|||
target = bank->target;
|
||||
|
||||
if (target->state != TARGET_HALTED) {
|
||||
LOG_ERROR("Target not halted");
|
||||
return ERROR_TARGET_NOT_HALTED;
|
||||
LOG_INFO("Target not halted");
|
||||
/* return ERROR_TARGET_NOT_HALTED; */
|
||||
}
|
||||
|
||||
if (stm32x_read_options(bank) != ERROR_OK) {
|
||||
|
@ -1203,8 +1308,8 @@ COMMAND_HANDLER(stm32x_handle_unlock_command)
|
|||
target = bank->target;
|
||||
|
||||
if (target->state != TARGET_HALTED) {
|
||||
LOG_ERROR("Target not halted");
|
||||
return ERROR_TARGET_NOT_HALTED;
|
||||
LOG_INFO("Target not halted");
|
||||
/* return ERROR_TARGET_NOT_HALTED; */
|
||||
}
|
||||
|
||||
if (stm32x_read_options(bank) != ERROR_OK) {
|
||||
|
@ -1215,6 +1320,9 @@ COMMAND_HANDLER(stm32x_handle_unlock_command)
|
|||
/* clear readout protection and complementary option bytes
|
||||
* this will also force a device unlock if set */
|
||||
stm32x_info->option_bytes.RDP = 0xAA;
|
||||
if (stm32x_info->has_optcr2_pcrop) {
|
||||
stm32x_info->option_bytes.optcr2_pcrop = OPTCR2_PCROP_RDP | (~1 << bank->num_sectors);
|
||||
}
|
||||
|
||||
if (stm32x_write_options(bank) != ERROR_OK) {
|
||||
command_print(CMD_CTX, "%s failed to unlock device", bank->driver->name);
|
||||
|
@ -1327,8 +1435,12 @@ COMMAND_HANDLER(stm32f2x_handle_options_read_command)
|
|||
" boot_add0 0x%04X, boot_add1 0x%04X",
|
||||
stm32x_info->option_bytes.user_options,
|
||||
boot_addr & 0xffff, (boot_addr & 0xffff0000) >> 16);
|
||||
if (stm32x_info->has_optcr2_pcrop) {
|
||||
command_print(CMD_CTX, "stm32f2x optcr2_pcrop 0x%08X",
|
||||
stm32x_info->option_bytes.optcr2_pcrop);
|
||||
}
|
||||
} else {
|
||||
command_print(CMD_CTX, "stm32f2x user_options 0x%03X,",
|
||||
command_print(CMD_CTX, "stm32f2x user_options 0x%03X",
|
||||
stm32x_info->option_bytes.user_options);
|
||||
}
|
||||
} else {
|
||||
|
@ -1345,7 +1457,7 @@ COMMAND_HANDLER(stm32f2x_handle_options_write_command)
|
|||
int retval;
|
||||
struct flash_bank *bank;
|
||||
struct stm32x_flash_bank *stm32x_info = NULL;
|
||||
uint16_t user_options, boot_addr0, boot_addr1;
|
||||
uint16_t user_options, boot_addr0, boot_addr1, options_mask;
|
||||
|
||||
if (CMD_ARGC < 1) {
|
||||
command_print(CMD_CTX, "stm32f2x options_write <bank> ...");
|
||||
|
@ -1378,9 +1490,11 @@ COMMAND_HANDLER(stm32f2x_handle_options_write_command)
|
|||
}
|
||||
|
||||
COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], user_options);
|
||||
if (user_options & (stm32x_info->has_extra_options ? ~0xffc : ~0xfc)) {
|
||||
options_mask = !stm32x_info->has_extra_options ? ~0xfc :
|
||||
~(((0xf00 << (stm32x_info->protection_bits - 12)) | 0xff) & 0xffc);
|
||||
if (user_options & options_mask) {
|
||||
command_print(CMD_CTX, "stm32f2x invalid user_options");
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
return ERROR_COMMAND_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
stm32x_info->option_bytes.user_options = user_options;
|
||||
|
@ -1400,6 +1514,48 @@ COMMAND_HANDLER(stm32f2x_handle_options_write_command)
|
|||
return retval;
|
||||
}
|
||||
|
||||
COMMAND_HANDLER(stm32f2x_handle_optcr2_write_command)
|
||||
{
|
||||
int retval;
|
||||
struct flash_bank *bank;
|
||||
struct stm32x_flash_bank *stm32x_info = NULL;
|
||||
uint32_t optcr2_pcrop;
|
||||
|
||||
if (CMD_ARGC != 2) {
|
||||
command_print(CMD_CTX, "stm32f2x optcr2_write <bank> <optcr2_value>");
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
|
||||
if (ERROR_OK != retval)
|
||||
return retval;
|
||||
|
||||
stm32x_info = bank->driver_priv;
|
||||
if (!stm32x_info->has_optcr2_pcrop) {
|
||||
command_print(CMD_CTX, "no optcr2 register");
|
||||
return ERROR_COMMAND_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
command_print(CMD_CTX, "INFO: To disable PCROP, set PCROP_RDP"
|
||||
" with PCROPi bits STILL SET, then\nlock device and"
|
||||
" finally unlock it. Clears PCROP and mass erases flash.");
|
||||
|
||||
retval = stm32x_read_options(bank);
|
||||
if (ERROR_OK != retval)
|
||||
return retval;
|
||||
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], optcr2_pcrop);
|
||||
stm32x_info->option_bytes.optcr2_pcrop = optcr2_pcrop;
|
||||
|
||||
if (stm32x_write_options(bank) != ERROR_OK) {
|
||||
command_print(CMD_CTX, "stm32f2x failed to write options");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
command_print(CMD_CTX, "stm32f2x optcr2_write complete.");
|
||||
return retval;
|
||||
}
|
||||
|
||||
static const struct command_registration stm32x_exec_command_handlers[] = {
|
||||
{
|
||||
.name = "lock",
|
||||
|
@ -1433,9 +1589,17 @@ static const struct command_registration stm32x_exec_command_handlers[] = {
|
|||
.name = "options_write",
|
||||
.handler = stm32f2x_handle_options_write_command,
|
||||
.mode = COMMAND_EXEC,
|
||||
.usage = "bank_id user_options [ boot_add0 boot_add1]",
|
||||
.usage = "bank_id user_options [ boot_add0 boot_add1 ]",
|
||||
.help = "Write option bytes",
|
||||
},
|
||||
{
|
||||
.name = "optcr2_write",
|
||||
.handler = stm32f2x_handle_optcr2_write_command,
|
||||
.mode = COMMAND_EXEC,
|
||||
.usage = "bank_id optcr2",
|
||||
.help = "Write optcr2 word",
|
||||
},
|
||||
|
||||
COMMAND_REGISTRATION_DONE
|
||||
};
|
||||
|
||||
|
|
|
@ -790,6 +790,11 @@ static int stm32lx_probe(struct flash_bank *bank)
|
|||
flash_size_in_kb = 256;
|
||||
}
|
||||
|
||||
/* 0x429 devices only use the lowest 8 bits of the flash size register */
|
||||
if (retval == ERROR_OK && (device_id & 0xfff) == 0x429) {
|
||||
flash_size_in_kb &= 0xff;
|
||||
}
|
||||
|
||||
/* Failed reading flash size or flash size invalid (early silicon),
|
||||
* default to max target family */
|
||||
if (retval != ERROR_OK || flash_size_in_kb == 0xffff || flash_size_in_kb == 0) {
|
||||
|
|
|
@ -583,9 +583,10 @@ COMMAND_HANDLER(handle_flash_write_bank_command)
|
|||
{
|
||||
uint32_t offset;
|
||||
uint8_t *buffer;
|
||||
size_t length;
|
||||
struct fileio *fileio;
|
||||
|
||||
if (CMD_ARGC != 3)
|
||||
if (CMD_ARGC < 2 || CMD_ARGC > 3)
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
|
||||
struct duration bench;
|
||||
|
@ -596,7 +597,16 @@ COMMAND_HANDLER(handle_flash_write_bank_command)
|
|||
if (ERROR_OK != retval)
|
||||
return retval;
|
||||
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], offset);
|
||||
offset = 0;
|
||||
|
||||
if (CMD_ARGC > 2)
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], offset);
|
||||
|
||||
if (offset > p->size) {
|
||||
LOG_ERROR("Offset 0x%8.8" PRIx32 " is out of range of the flash bank",
|
||||
offset);
|
||||
return ERROR_COMMAND_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
if (fileio_open(&fileio, CMD_ARGV[1], FILEIO_READ, FILEIO_BINARY) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
|
@ -608,20 +618,38 @@ COMMAND_HANDLER(handle_flash_write_bank_command)
|
|||
return retval;
|
||||
}
|
||||
|
||||
buffer = malloc(filesize);
|
||||
length = MIN(filesize, p->size - offset);
|
||||
|
||||
if (!length) {
|
||||
LOG_INFO("Nothing to write to flash bank");
|
||||
fileio_close(fileio);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
if (length != filesize)
|
||||
LOG_INFO("File content exceeds flash bank size. Only writing the "
|
||||
"first %zu bytes of the file", length);
|
||||
|
||||
buffer = malloc(length);
|
||||
if (buffer == NULL) {
|
||||
fileio_close(fileio);
|
||||
LOG_ERROR("Out of memory");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
size_t buf_cnt;
|
||||
if (fileio_read(fileio, filesize, buffer, &buf_cnt) != ERROR_OK) {
|
||||
if (fileio_read(fileio, length, buffer, &buf_cnt) != ERROR_OK) {
|
||||
free(buffer);
|
||||
fileio_close(fileio);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
retval = flash_driver_write(p, buffer, offset, buf_cnt);
|
||||
if (buf_cnt != length) {
|
||||
LOG_ERROR("Short read");
|
||||
free(buffer);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
retval = flash_driver_write(p, buffer, offset, length);
|
||||
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
|
@ -629,8 +657,8 @@ COMMAND_HANDLER(handle_flash_write_bank_command)
|
|||
if ((ERROR_OK == retval) && (duration_measure(&bench) == ERROR_OK)) {
|
||||
command_print(CMD_CTX, "wrote %zu bytes from file %s to flash bank %u"
|
||||
" at offset 0x%8.8" PRIx32 " in %fs (%0.3f KiB/s)",
|
||||
filesize, CMD_ARGV[1], p->bank_number, offset,
|
||||
duration_elapsed(&bench), duration_kbps(&bench, filesize));
|
||||
length, CMD_ARGV[1], p->bank_number, offset,
|
||||
duration_elapsed(&bench), duration_kbps(&bench, length));
|
||||
}
|
||||
|
||||
fileio_close(fileio);
|
||||
|
@ -646,7 +674,7 @@ COMMAND_HANDLER(handle_flash_read_bank_command)
|
|||
uint32_t length;
|
||||
size_t written;
|
||||
|
||||
if (CMD_ARGC != 4)
|
||||
if (CMD_ARGC < 2 || CMD_ARGC > 4)
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
|
||||
struct duration bench;
|
||||
|
@ -654,11 +682,31 @@ COMMAND_HANDLER(handle_flash_read_bank_command)
|
|||
|
||||
struct flash_bank *p;
|
||||
int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &p);
|
||||
|
||||
if (ERROR_OK != retval)
|
||||
return retval;
|
||||
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], offset);
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], length);
|
||||
offset = 0;
|
||||
|
||||
if (CMD_ARGC > 2)
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], offset);
|
||||
|
||||
if (offset > p->size) {
|
||||
LOG_ERROR("Offset 0x%8.8" PRIx32 " is out of range of the flash bank",
|
||||
offset);
|
||||
return ERROR_COMMAND_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
length = p->size - offset;
|
||||
|
||||
if (CMD_ARGC > 3)
|
||||
COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], length);
|
||||
|
||||
if (offset + length > p->size) {
|
||||
LOG_ERROR("Length of %" PRIu32 " bytes with offset 0x%8.8" PRIx32
|
||||
" is out of range of the flash bank", length, offset);
|
||||
return ERROR_COMMAND_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
buffer = malloc(length);
|
||||
if (buffer == NULL) {
|
||||
|
@ -705,6 +753,7 @@ COMMAND_HANDLER(handle_flash_verify_bank_command)
|
|||
struct fileio *fileio;
|
||||
size_t read_cnt;
|
||||
size_t filesize;
|
||||
size_t length;
|
||||
int differ;
|
||||
|
||||
if (CMD_ARGC < 2 || CMD_ARGC > 3)
|
||||
|
@ -741,14 +790,26 @@ COMMAND_HANDLER(handle_flash_verify_bank_command)
|
|||
return retval;
|
||||
}
|
||||
|
||||
buffer_file = malloc(filesize);
|
||||
length = MIN(filesize, p->size - offset);
|
||||
|
||||
if (!length) {
|
||||
LOG_INFO("Nothing to compare with flash bank");
|
||||
fileio_close(fileio);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
if (length != filesize)
|
||||
LOG_INFO("File content exceeds flash bank size. Only comparing the "
|
||||
"first %zu bytes of the file", length);
|
||||
|
||||
buffer_file = malloc(length);
|
||||
if (buffer_file == NULL) {
|
||||
LOG_ERROR("Out of memory");
|
||||
fileio_close(fileio);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
retval = fileio_read(fileio, filesize, buffer_file, &read_cnt);
|
||||
retval = fileio_read(fileio, length, buffer_file, &read_cnt);
|
||||
fileio_close(fileio);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("File read failure");
|
||||
|
@ -756,20 +817,20 @@ COMMAND_HANDLER(handle_flash_verify_bank_command)
|
|||
return retval;
|
||||
}
|
||||
|
||||
if (read_cnt != filesize) {
|
||||
if (read_cnt != length) {
|
||||
LOG_ERROR("Short read");
|
||||
free(buffer_file);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
buffer_flash = malloc(filesize);
|
||||
buffer_flash = malloc(length);
|
||||
if (buffer_flash == NULL) {
|
||||
LOG_ERROR("Out of memory");
|
||||
free(buffer_file);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
retval = flash_driver_read(p, buffer_flash, offset, read_cnt);
|
||||
retval = flash_driver_read(p, buffer_flash, offset, length);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("Flash read error");
|
||||
free(buffer_flash);
|
||||
|
@ -780,15 +841,15 @@ COMMAND_HANDLER(handle_flash_verify_bank_command)
|
|||
if (duration_measure(&bench) == ERROR_OK)
|
||||
command_print(CMD_CTX, "read %zd bytes from file %s and flash bank %u"
|
||||
" at offset 0x%8.8" PRIx32 " in %fs (%0.3f KiB/s)",
|
||||
read_cnt, CMD_ARGV[1], p->bank_number, offset,
|
||||
duration_elapsed(&bench), duration_kbps(&bench, read_cnt));
|
||||
length, CMD_ARGV[1], p->bank_number, offset,
|
||||
duration_elapsed(&bench), duration_kbps(&bench, length));
|
||||
|
||||
differ = memcmp(buffer_file, buffer_flash, read_cnt);
|
||||
differ = memcmp(buffer_file, buffer_flash, length);
|
||||
command_print(CMD_CTX, "contents %s", differ ? "differ" : "match");
|
||||
if (differ) {
|
||||
uint32_t t;
|
||||
int diffs = 0;
|
||||
for (t = 0; t < read_cnt; t++) {
|
||||
for (t = 0; t < length; t++) {
|
||||
if (buffer_flash[t] == buffer_file[t])
|
||||
continue;
|
||||
command_print(CMD_CTX, "diff %d address 0x%08x. Was 0x%02x instead of 0x%02x",
|
||||
|
@ -908,10 +969,9 @@ static const struct command_registration flash_exec_command_handlers[] = {
|
|||
.name = "write_bank",
|
||||
.handler = handle_flash_write_bank_command,
|
||||
.mode = COMMAND_EXEC,
|
||||
.usage = "bank_id filename offset",
|
||||
.help = "Write binary data from file to flash bank, "
|
||||
"starting at specified byte offset from the "
|
||||
"beginning of the bank.",
|
||||
.usage = "bank_id filename [offset]",
|
||||
.help = "Write binary data from file to flash bank. Allow optional "
|
||||
"offset from beginning of the bank (defaults to zero).",
|
||||
},
|
||||
{
|
||||
.name = "write_image",
|
||||
|
@ -926,10 +986,9 @@ static const struct command_registration flash_exec_command_handlers[] = {
|
|||
.name = "read_bank",
|
||||
.handler = handle_flash_read_bank_command,
|
||||
.mode = COMMAND_EXEC,
|
||||
.usage = "bank_id filename offset length",
|
||||
.help = "Read binary data from flash bank to file, "
|
||||
"starting at specified byte offset from the "
|
||||
"beginning of the bank.",
|
||||
.usage = "bank_id filename [offset [length]]",
|
||||
.help = "Read binary data from flash bank to file. Allow optional "
|
||||
"offset from beginning of the bank (defaults to zero).",
|
||||
},
|
||||
{
|
||||
.name = "verify_bank",
|
||||
|
|
|
@ -50,11 +50,12 @@ static int64_t current_time;
|
|||
|
||||
static int64_t start;
|
||||
|
||||
static const char * const log_strings[5] = {
|
||||
static const char * const log_strings[6] = {
|
||||
"User : ",
|
||||
"Error: ",
|
||||
"Warn : ", /* want a space after each colon, all same width, colons aligned */
|
||||
"Info : ",
|
||||
"Debug: ",
|
||||
"Debug: "
|
||||
};
|
||||
|
||||
|
@ -234,8 +235,8 @@ COMMAND_HANDLER(handle_debug_level_command)
|
|||
if (CMD_ARGC == 1) {
|
||||
int new_level;
|
||||
COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], new_level);
|
||||
if ((new_level > LOG_LVL_DEBUG) || (new_level < LOG_LVL_SILENT)) {
|
||||
LOG_ERROR("level must be between %d and %d", LOG_LVL_SILENT, LOG_LVL_DEBUG);
|
||||
if ((new_level > LOG_LVL_DEBUG_IO) || (new_level < LOG_LVL_SILENT)) {
|
||||
LOG_ERROR("level must be between %d and %d", LOG_LVL_SILENT, LOG_LVL_DEBUG_IO);
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
debug_level = new_level;
|
||||
|
@ -279,7 +280,8 @@ static struct command_registration log_command_handlers[] = {
|
|||
.mode = COMMAND_ANY,
|
||||
.help = "Sets the verbosity level of debugging output. "
|
||||
"0 shows errors only; 1 adds warnings; "
|
||||
"2 (default) adds other info; 3 adds debugging.",
|
||||
"2 (default) adds other info; 3 adds debugging; "
|
||||
"4 adds extra verbose debugging.",
|
||||
.usage = "number",
|
||||
},
|
||||
COMMAND_REGISTRATION_DONE
|
||||
|
@ -303,7 +305,7 @@ void log_init(void)
|
|||
int retval = parse_int(debug_env, &value);
|
||||
if (ERROR_OK == retval &&
|
||||
debug_level >= LOG_LVL_SILENT &&
|
||||
debug_level <= LOG_LVL_DEBUG)
|
||||
debug_level <= LOG_LVL_DEBUG_IO)
|
||||
debug_level = value;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
* LOG_LVL_WARNING - non-fatal errors, that may be resolved later
|
||||
* LOG_LVL_INFO - state information, etc.
|
||||
* LOG_LVL_DEBUG - debug statements, execution trace
|
||||
* LOG_LVL_DEBUG_IO - verbose debug, low-level I/O trace
|
||||
*/
|
||||
enum log_levels {
|
||||
LOG_LVL_SILENT = -3,
|
||||
|
@ -54,7 +55,8 @@ enum log_levels {
|
|||
LOG_LVL_ERROR = 0,
|
||||
LOG_LVL_WARNING = 1,
|
||||
LOG_LVL_INFO = 2,
|
||||
LOG_LVL_DEBUG = 3
|
||||
LOG_LVL_DEBUG = 3,
|
||||
LOG_LVL_DEBUG_IO = 4,
|
||||
};
|
||||
|
||||
void log_printf(enum log_levels level, const char *file, unsigned line,
|
||||
|
@ -102,6 +104,14 @@ extern int debug_level;
|
|||
|
||||
#define LOG_LEVEL_IS(FOO) ((debug_level) >= (FOO))
|
||||
|
||||
#define LOG_DEBUG_IO(expr ...) \
|
||||
do { \
|
||||
if (debug_level >= LOG_LVL_DEBUG_IO) \
|
||||
log_printf_lf(LOG_LVL_DEBUG, \
|
||||
__FILE__, __LINE__, __func__, \
|
||||
expr); \
|
||||
} while (0)
|
||||
|
||||
#define LOG_DEBUG(expr ...) \
|
||||
do { \
|
||||
if (debug_level >= LOG_LVL_DEBUG) \
|
||||
|
|
|
@ -185,10 +185,11 @@ static void bitbang_stableclocks(int num_cycles)
|
|||
}
|
||||
}
|
||||
|
||||
static void bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer, int scan_size)
|
||||
static void bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer,
|
||||
unsigned scan_size)
|
||||
{
|
||||
tap_state_t saved_end_state = tap_get_end_state();
|
||||
int bit_cnt;
|
||||
unsigned bit_cnt;
|
||||
|
||||
if (!((!ir_scan &&
|
||||
(tap_get_state() == TAP_DRSHIFT)) ||
|
||||
|
@ -202,8 +203,8 @@ static void bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer, int
|
|||
bitbang_end_state(saved_end_state);
|
||||
}
|
||||
|
||||
size_t buffered = 0;
|
||||
for (bit_cnt = 0; bit_cnt < scan_size; bit_cnt++) {
|
||||
int val = 0;
|
||||
int tms = (bit_cnt == scan_size-1) ? 1 : 0;
|
||||
int tdi;
|
||||
int bytec = bit_cnt/8;
|
||||
|
@ -219,16 +220,31 @@ static void bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer, int
|
|||
|
||||
bitbang_interface->write(0, tms, tdi);
|
||||
|
||||
if (type != SCAN_OUT)
|
||||
val = bitbang_interface->read();
|
||||
if (type != SCAN_OUT) {
|
||||
if (bitbang_interface->buf_size) {
|
||||
bitbang_interface->sample();
|
||||
buffered++;
|
||||
} else {
|
||||
int val = bitbang_interface->read();
|
||||
if (val)
|
||||
buffer[bytec] |= bcval;
|
||||
else
|
||||
buffer[bytec] &= ~bcval;
|
||||
}
|
||||
}
|
||||
|
||||
bitbang_interface->write(1, tms, tdi);
|
||||
|
||||
if (type != SCAN_OUT) {
|
||||
if (val)
|
||||
buffer[bytec] |= bcval;
|
||||
else
|
||||
buffer[bytec] &= ~bcval;
|
||||
if (type != SCAN_OUT && bitbang_interface->buf_size &&
|
||||
(buffered == bitbang_interface->buf_size ||
|
||||
bit_cnt == scan_size - 1)) {
|
||||
for (unsigned i = bit_cnt + 1 - buffered; i <= bit_cnt; i++) {
|
||||
if (bitbang_interface->read_sample())
|
||||
buffer[i/8] |= 1 << (i % 8);
|
||||
else
|
||||
buffer[i/8] &= ~(1 << (i % 8));
|
||||
}
|
||||
buffered = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,13 +325,14 @@ int bitbang_execute_queue(void)
|
|||
bitbang_path_move(cmd->cmd.pathmove);
|
||||
break;
|
||||
case JTAG_SCAN:
|
||||
#ifdef _DEBUG_JTAG_IO_
|
||||
LOG_DEBUG("%s scan end in %s",
|
||||
(cmd->cmd.scan->ir_scan) ? "IR" : "DR",
|
||||
tap_state_name(cmd->cmd.scan->end_state));
|
||||
#endif
|
||||
bitbang_end_state(cmd->cmd.scan->end_state);
|
||||
scan_size = jtag_build_buffer(cmd->cmd.scan, &buffer);
|
||||
#ifdef _DEBUG_JTAG_IO_
|
||||
LOG_DEBUG("%s scan %d bits; end in %s",
|
||||
(cmd->cmd.scan->ir_scan) ? "IR" : "DR",
|
||||
scan_size,
|
||||
tap_state_name(cmd->cmd.scan->end_state));
|
||||
#endif
|
||||
type = jtag_scan_type(cmd->cmd.scan);
|
||||
bitbang_scan(cmd->cmd.scan->ir_scan, type, buffer, scan_size);
|
||||
if (jtag_read_buffer(buffer, cmd->cmd.scan) != ERROR_OK)
|
||||
|
|
|
@ -27,7 +27,24 @@
|
|||
struct bitbang_interface {
|
||||
/* low level callbacks (for bitbang)
|
||||
*/
|
||||
|
||||
/* Either read() or sample()/read_sample() must be implemented. */
|
||||
|
||||
/* Sample TDO and return 0 or 1. */
|
||||
int (*read)(void);
|
||||
|
||||
/* The sample functions allow an interface to batch a number of writes and
|
||||
* sample requests together. Not waiting for a value to come back can
|
||||
* greatly increase throughput. */
|
||||
/* The number of TDO samples that can be buffered up before the caller has
|
||||
* to call read_sample. */
|
||||
size_t buf_size;
|
||||
/* Sample TDO and put the result in a buffer. */
|
||||
void (*sample)(void);
|
||||
/* Return the next unread value from the buffer. */
|
||||
int (*read_sample)(void);
|
||||
|
||||
/* Set TCK, TMS, and TDI to the given values. */
|
||||
void (*write)(int tck, int tms, int tdi);
|
||||
void (*reset)(int trst, int srst);
|
||||
void (*blink)(int on);
|
||||
|
|
|
@ -596,7 +596,7 @@ static int cmsis_dap_swd_run_queue(void)
|
|||
{
|
||||
uint8_t *buffer = cmsis_dap_handle->packet_buffer;
|
||||
|
||||
LOG_DEBUG("Executing %d queued transactions", pending_transfer_count);
|
||||
LOG_DEBUG_IO("Executing %d queued transactions", pending_transfer_count);
|
||||
|
||||
if (queued_retval != ERROR_OK) {
|
||||
LOG_DEBUG("Skipping due to previous errors: %d", queued_retval);
|
||||
|
@ -616,7 +616,7 @@ static int cmsis_dap_swd_run_queue(void)
|
|||
uint8_t cmd = pending_transfers[i].cmd;
|
||||
uint32_t data = pending_transfers[i].data;
|
||||
|
||||
LOG_DEBUG("%s %s reg %x %"PRIx32,
|
||||
LOG_DEBUG_IO("%s %s reg %x %"PRIx32,
|
||||
cmd & SWD_CMD_APnDP ? "AP" : "DP",
|
||||
cmd & SWD_CMD_RnW ? "read" : "write",
|
||||
(cmd & SWD_CMD_A32) >> 1, data);
|
||||
|
@ -674,7 +674,7 @@ static int cmsis_dap_swd_run_queue(void)
|
|||
uint32_t tmp = data;
|
||||
idx += 4;
|
||||
|
||||
LOG_DEBUG("Read result: %"PRIx32, data);
|
||||
LOG_DEBUG_IO("Read result: %"PRIx32, data);
|
||||
|
||||
/* Imitate posted AP reads */
|
||||
if ((pending_transfers[i].cmd & SWD_CMD_APnDP) ||
|
||||
|
|
|
@ -430,40 +430,10 @@ static void ftdi_execute_pathmove(struct jtag_command *cmd)
|
|||
tap_set_end_state(tap_get_state());
|
||||
}
|
||||
|
||||
#ifdef _DEBUG_JTAG_IO_
|
||||
static void debug_jtag_io_value(const char *prefix, const uint8_t *value,
|
||||
unsigned int num_bits)
|
||||
{
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[33];
|
||||
char *bufp = buf;
|
||||
unsigned int chars = (num_bits + 3) / 4;
|
||||
for (unsigned int i = 0; i < chars; i++) {
|
||||
if (i && (i % 32) == 0) {
|
||||
DEBUG_JTAG_IO(" %s%s", prefix, buf);
|
||||
bufp = buf;
|
||||
}
|
||||
int start_bit = 4 * (chars - i - 1);
|
||||
sprintf(bufp, "%01x", buf_get_u32(value, start_bit, 4));
|
||||
bufp++;
|
||||
}
|
||||
if (bufp != buf) {
|
||||
DEBUG_JTAG_IO(" %s%s", prefix, buf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ftdi_execute_scan(struct jtag_command *cmd)
|
||||
{
|
||||
DEBUG_JTAG_IO("%s type:%d", cmd->cmd.scan->ir_scan ? "IRSCAN" : "DRSCAN",
|
||||
jtag_scan_type(cmd->cmd.scan));
|
||||
#ifdef _DEBUG_JTAG_IO_
|
||||
debug_jtag_io_value(" out=", cmd->cmd.scan->fields->out_value,
|
||||
cmd->cmd.scan->fields->num_bits);
|
||||
#endif
|
||||
|
||||
/* Make sure there are no trailing fields with num_bits == 0, or the logic below will fail. */
|
||||
while (cmd->cmd.scan->num_fields > 0
|
||||
|
@ -545,11 +515,6 @@ static void ftdi_execute_scan(struct jtag_command *cmd)
|
|||
DEBUG_JTAG_IO("%s scan, %i bits, end in %s",
|
||||
(cmd->cmd.scan->ir_scan) ? "IR" : "DR", scan_size,
|
||||
tap_state_name(tap_get_end_state()));
|
||||
|
||||
#ifdef _DEBUG_JTAG_IO_
|
||||
debug_jtag_io_value(" in=", cmd->cmd.scan->fields->in_value,
|
||||
cmd->cmd.scan->fields->num_bits);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ftdi_execute_reset(struct jtag_command *cmd)
|
||||
|
@ -1109,12 +1074,12 @@ static void ftdi_swd_swdio_en(bool enable)
|
|||
*/
|
||||
static int ftdi_swd_run_queue(void)
|
||||
{
|
||||
LOG_DEBUG("Executing %zu queued transactions", swd_cmd_queue_length);
|
||||
LOG_DEBUG_IO("Executing %zu queued transactions", swd_cmd_queue_length);
|
||||
int retval;
|
||||
struct signal *led = find_signal_by_name("LED");
|
||||
|
||||
if (queued_retval != ERROR_OK) {
|
||||
LOG_DEBUG("Skipping due to previous errors: %d", queued_retval);
|
||||
LOG_DEBUG_IO("Skipping due to previous errors: %d", queued_retval);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
|
@ -1135,7 +1100,7 @@ static int ftdi_swd_run_queue(void)
|
|||
for (size_t i = 0; i < swd_cmd_queue_length; i++) {
|
||||
int ack = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3);
|
||||
|
||||
LOG_DEBUG("%s %s %s reg %X = %08"PRIx32,
|
||||
LOG_DEBUG_IO("%s %s %s reg %X = %08"PRIx32,
|
||||
ack == SWD_ACK_OK ? "OK" : ack == SWD_ACK_WAIT ? "WAIT" : ack == SWD_ACK_FAULT ? "FAULT" : "JUNK",
|
||||
swd_cmd_queue[i].cmd & SWD_CMD_APnDP ? "AP" : "DP",
|
||||
swd_cmd_queue[i].cmd & SWD_CMD_RnW ? "read" : "write",
|
||||
|
|
|
@ -40,8 +40,58 @@
|
|||
static char *remote_bitbang_host;
|
||||
static char *remote_bitbang_port;
|
||||
|
||||
FILE *remote_bitbang_in;
|
||||
FILE *remote_bitbang_out;
|
||||
static FILE *remote_bitbang_in;
|
||||
static FILE *remote_bitbang_out;
|
||||
static int remote_bitbang_fd;
|
||||
|
||||
/* Circular buffer. When start == end, the buffer is empty. */
|
||||
static char remote_bitbang_buf[64];
|
||||
static unsigned remote_bitbang_start;
|
||||
static unsigned remote_bitbang_end;
|
||||
|
||||
static int remote_bitbang_buf_full(void)
|
||||
{
|
||||
return remote_bitbang_end ==
|
||||
((remote_bitbang_start + sizeof(remote_bitbang_buf) - 1) %
|
||||
sizeof(remote_bitbang_buf));
|
||||
}
|
||||
|
||||
/* Read any incoming data, placing it into the buffer. */
|
||||
static void remote_bitbang_fill_buf(void)
|
||||
{
|
||||
fcntl(remote_bitbang_fd, F_SETFL, O_NONBLOCK);
|
||||
while (!remote_bitbang_buf_full()) {
|
||||
unsigned contiguous_available_space;
|
||||
if (remote_bitbang_end >= remote_bitbang_start) {
|
||||
contiguous_available_space = sizeof(remote_bitbang_buf) -
|
||||
remote_bitbang_end;
|
||||
if (remote_bitbang_start == 0)
|
||||
contiguous_available_space -= 1;
|
||||
} else {
|
||||
contiguous_available_space = remote_bitbang_start -
|
||||
remote_bitbang_end - 1;
|
||||
}
|
||||
ssize_t count = read(remote_bitbang_fd,
|
||||
remote_bitbang_buf + remote_bitbang_end,
|
||||
contiguous_available_space);
|
||||
if (count > 0) {
|
||||
remote_bitbang_end += count;
|
||||
// TODO: check for overflow.
|
||||
if (remote_bitbang_end == sizeof(remote_bitbang_buf)) {
|
||||
remote_bitbang_end = 0;
|
||||
}
|
||||
} else if (count == 0) {
|
||||
return;
|
||||
} else if (count < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return;
|
||||
} else {
|
||||
REMOTE_BITBANG_RAISE_ERROR("remote_bitbang_fill_buf: %s (%d)",
|
||||
strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void remote_bitbang_putc(int c)
|
||||
{
|
||||
|
@ -75,15 +125,8 @@ static int remote_bitbang_quit(void)
|
|||
return ERROR_OK;
|
||||
}
|
||||
|
||||
/* Get the next read response. */
|
||||
static int remote_bitbang_rread(void)
|
||||
static int char_to_int(int c)
|
||||
{
|
||||
if (EOF == fflush(remote_bitbang_out)) {
|
||||
remote_bitbang_quit();
|
||||
REMOTE_BITBANG_RAISE_ERROR("fflush: %s", strerror(errno));
|
||||
}
|
||||
|
||||
int c = fgetc(remote_bitbang_in);
|
||||
switch (c) {
|
||||
case '0':
|
||||
return 0;
|
||||
|
@ -96,9 +139,42 @@ static int remote_bitbang_rread(void)
|
|||
}
|
||||
}
|
||||
|
||||
static int remote_bitbang_read(void)
|
||||
/* Get the next read response. */
|
||||
static int remote_bitbang_rread(void)
|
||||
{
|
||||
if (EOF == fflush(remote_bitbang_out)) {
|
||||
remote_bitbang_quit();
|
||||
REMOTE_BITBANG_RAISE_ERROR("fflush: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* Enable blocking access. */
|
||||
fcntl(remote_bitbang_fd, F_SETFL, 0);
|
||||
char c;
|
||||
ssize_t count = read(remote_bitbang_fd, &c, 1);
|
||||
if (count == 1) {
|
||||
return char_to_int(c);
|
||||
} else {
|
||||
remote_bitbang_quit();
|
||||
REMOTE_BITBANG_RAISE_ERROR("read: count=%d, error=%s", (int) count,
|
||||
strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void remote_bitbang_sample(void)
|
||||
{
|
||||
remote_bitbang_fill_buf();
|
||||
assert(!remote_bitbang_buf_full());
|
||||
remote_bitbang_putc('R');
|
||||
}
|
||||
|
||||
static int remote_bitbang_read_sample(void)
|
||||
{
|
||||
if (remote_bitbang_start != remote_bitbang_end) {
|
||||
int c = remote_bitbang_buf[remote_bitbang_start];
|
||||
remote_bitbang_start =
|
||||
(remote_bitbang_start + 1) % sizeof(remote_bitbang_buf);
|
||||
return char_to_int(c);
|
||||
}
|
||||
return remote_bitbang_rread();
|
||||
}
|
||||
|
||||
|
@ -121,7 +197,9 @@ static void remote_bitbang_blink(int on)
|
|||
}
|
||||
|
||||
static struct bitbang_interface remote_bitbang_bitbang = {
|
||||
.read = &remote_bitbang_read,
|
||||
.buf_size = sizeof(remote_bitbang_buf) - 1,
|
||||
.sample = &remote_bitbang_sample,
|
||||
.read_sample = &remote_bitbang_read_sample,
|
||||
.write = &remote_bitbang_write,
|
||||
.reset = &remote_bitbang_reset,
|
||||
.blink = &remote_bitbang_blink,
|
||||
|
@ -199,26 +277,28 @@ static int remote_bitbang_init_unix(void)
|
|||
|
||||
static int remote_bitbang_init(void)
|
||||
{
|
||||
int fd;
|
||||
bitbang_interface = &remote_bitbang_bitbang;
|
||||
|
||||
remote_bitbang_start = 0;
|
||||
remote_bitbang_end = 0;
|
||||
|
||||
LOG_INFO("Initializing remote_bitbang driver");
|
||||
if (remote_bitbang_port == NULL)
|
||||
fd = remote_bitbang_init_unix();
|
||||
remote_bitbang_fd = remote_bitbang_init_unix();
|
||||
else
|
||||
fd = remote_bitbang_init_tcp();
|
||||
remote_bitbang_fd = remote_bitbang_init_tcp();
|
||||
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
if (remote_bitbang_fd < 0)
|
||||
return remote_bitbang_fd;
|
||||
|
||||
remote_bitbang_in = fdopen(fd, "r");
|
||||
remote_bitbang_in = fdopen(remote_bitbang_fd, "r");
|
||||
if (remote_bitbang_in == NULL) {
|
||||
LOG_ERROR("fdopen: failed to open read stream");
|
||||
close(fd);
|
||||
close(remote_bitbang_fd);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
remote_bitbang_out = fdopen(fd, "w");
|
||||
remote_bitbang_out = fdopen(remote_bitbang_fd, "w");
|
||||
if (remote_bitbang_out == NULL) {
|
||||
LOG_ERROR("fdopen: failed to open write stream");
|
||||
fclose(remote_bitbang_in);
|
||||
|
|
|
@ -30,7 +30,6 @@ static int riscv_create_rtos(struct target *target)
|
|||
|
||||
target->rtos->current_threadid = 1;
|
||||
target->rtos->current_thread = 1;
|
||||
riscv_update_threads(target->rtos);
|
||||
|
||||
target->rtos->gdb_thread_packet = riscv_gdb_thread_packet;
|
||||
target->rtos->gdb_v_packet = riscv_gdb_v_packet;
|
||||
|
@ -42,6 +41,8 @@ int riscv_update_threads(struct rtos *rtos)
|
|||
{
|
||||
LOG_DEBUG("Updating the RISC-V Hart List");
|
||||
|
||||
struct target *target = rtos->target;
|
||||
|
||||
/* Figures out how many harts there are on the system. */
|
||||
int hart_count = riscv_count_harts(rtos->target);
|
||||
if (rtos->thread_count != hart_count) {
|
||||
|
@ -54,7 +55,8 @@ int riscv_update_threads(struct rtos *rtos)
|
|||
rtos->thread_details[i].exists = true;
|
||||
if (asprintf(&rtos->thread_details[i].thread_name_str, "Hart %d", i) < 0)
|
||||
LOG_ERROR("riscv_update_threads() failed asprintf");
|
||||
if (asprintf(&rtos->thread_details[i].extra_info_str, "RV64") < 0)
|
||||
if (asprintf(&rtos->thread_details[i].extra_info_str, "RV%d",
|
||||
riscv_xlen_of_hart(target, i)) < 0)
|
||||
LOG_ERROR("riscv_update_threads() failed asprintf");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -434,7 +434,7 @@ int rtos_get_gdb_reg_list(struct connection *connection)
|
|||
(target->smp))) { /* in smp several current thread are possible */
|
||||
char *hex_reg_list;
|
||||
|
||||
LOG_INFO("RTOS: getting register list for thread 0x%" PRIx64
|
||||
LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
|
||||
", target->rtos->current_thread=0x%" PRIx64 "\r\n",
|
||||
current_threadid,
|
||||
target->rtos->current_thread);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2016 by Square, Inc. *
|
||||
* Copyright (C) 2017 by Square, Inc. *
|
||||
* Steven Stallion <stallion@squareup.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
|
@ -20,34 +20,35 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtos.h"
|
||||
#include "rtos_standard_stackings.h"
|
||||
#include "target/armv7m.h"
|
||||
#include <helper/types.h>
|
||||
#include <rtos/rtos.h>
|
||||
#include <rtos/rtos_standard_stackings.h>
|
||||
#include <target/armv7m.h>
|
||||
|
||||
static const struct stack_register_offset rtos_uCOS_III_Cortex_M_stack_offsets[ARMV7M_NUM_CORE_REGS] = {
|
||||
{ 0x20, 32 }, /* r0 */
|
||||
{ 0x24, 32 }, /* r1 */
|
||||
{ 0x28, 32 }, /* r2 */
|
||||
{ 0x2c, 32 }, /* r3 */
|
||||
{ 0x00, 32 }, /* r4 */
|
||||
{ 0x04, 32 }, /* r5 */
|
||||
{ 0x08, 32 }, /* r6 */
|
||||
{ 0x0c, 32 }, /* r7 */
|
||||
{ 0x10, 32 }, /* r8 */
|
||||
{ 0x14, 32 }, /* r9 */
|
||||
{ 0x18, 32 }, /* r10 */
|
||||
{ 0x1c, 32 }, /* r11 */
|
||||
{ 0x30, 32 }, /* r12 */
|
||||
{ -2, 32 }, /* sp */
|
||||
{ 0x34, 32 }, /* lr */
|
||||
{ 0x38, 32 }, /* pc */
|
||||
{ 0x3c, 32 }, /* xPSR */
|
||||
static const struct stack_register_offset rtos_uCOS_III_Cortex_M_stack_offsets[] = {
|
||||
{ 0x20, 32 }, /* r0 */
|
||||
{ 0x24, 32 }, /* r1 */
|
||||
{ 0x28, 32 }, /* r2 */
|
||||
{ 0x2c, 32 }, /* r3 */
|
||||
{ 0x00, 32 }, /* r4 */
|
||||
{ 0x04, 32 }, /* r5 */
|
||||
{ 0x08, 32 }, /* r6 */
|
||||
{ 0x0c, 32 }, /* r7 */
|
||||
{ 0x10, 32 }, /* r8 */
|
||||
{ 0x14, 32 }, /* r9 */
|
||||
{ 0x18, 32 }, /* r10 */
|
||||
{ 0x1c, 32 }, /* r11 */
|
||||
{ 0x30, 32 }, /* r12 */
|
||||
{ -2, 32 }, /* sp */
|
||||
{ 0x34, 32 }, /* lr */
|
||||
{ 0x38, 32 }, /* pc */
|
||||
{ 0x3c, 32 }, /* xPSR */
|
||||
};
|
||||
|
||||
const struct rtos_register_stacking rtos_uCOS_III_Cortex_M_stacking = {
|
||||
0x40, /* stack_registers_size */
|
||||
-1, /* stack_growth_direction */
|
||||
ARMV7M_NUM_CORE_REGS, /* num_output_registers */
|
||||
rtos_generic_stack_align8, /* stack_alignment */
|
||||
rtos_uCOS_III_Cortex_M_stack_offsets /* register_offsets */
|
||||
0x40, /* stack_registers_size */
|
||||
-1, /* stack_growth_direction */
|
||||
ARRAY_SIZE(rtos_uCOS_III_Cortex_M_stack_offsets), /* num_output_registers */
|
||||
rtos_generic_stack_align8, /* stack_alignment */
|
||||
rtos_uCOS_III_Cortex_M_stack_offsets /* register_offsets */
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2016 by Square, Inc. *
|
||||
* Copyright (C) 2017 by Square, Inc. *
|
||||
* Steven Stallion <stallion@squareup.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
|
@ -23,7 +23,7 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "rtos.h"
|
||||
#include <rtos/rtos.h>
|
||||
|
||||
extern const struct rtos_register_stacking rtos_uCOS_III_Cortex_M_stacking;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2016 by Square, Inc. *
|
||||
* Copyright (C) 2017 by Square, Inc. *
|
||||
* Steven Stallion <stallion@squareup.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
|
@ -20,14 +20,14 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <helper/log.h>
|
||||
#include <helper/time_support.h>
|
||||
#include <jtag/jtag.h>
|
||||
#include "target/target.h"
|
||||
#include "target/target_type.h"
|
||||
#include "rtos.h"
|
||||
#include "helper/log.h"
|
||||
#include "helper/types.h"
|
||||
#include "rtos/rtos_ucos_iii_stackings.h"
|
||||
#include <helper/types.h>
|
||||
#include <rtos/rtos.h>
|
||||
#include <target/target.h>
|
||||
#include <target/target_type.h>
|
||||
|
||||
#include "rtos_ucos_iii_stackings.h"
|
||||
|
||||
#ifndef UCOS_III_MAX_STRLEN
|
||||
#define UCOS_III_MAX_STRLEN 64
|
||||
|
@ -55,18 +55,18 @@ struct uCOS_III_params {
|
|||
|
||||
static const struct uCOS_III_params uCOS_III_params_list[] = {
|
||||
{
|
||||
"cortex_m", /* target_name */
|
||||
sizeof(uint32_t), /* pointer_width */
|
||||
0, /* thread_stack_offset */
|
||||
0, /* thread_name_offset */
|
||||
0, /* thread_state_offset */
|
||||
0, /* thread_priority_offset */
|
||||
0, /* thread_prev_offset */
|
||||
0, /* thread_next_offset */
|
||||
false, /* thread_offsets_updated */
|
||||
1, /* threadid_start */
|
||||
"cortex_m", /* target_name */
|
||||
sizeof(uint32_t), /* pointer_width */
|
||||
0, /* thread_stack_offset */
|
||||
0, /* thread_name_offset */
|
||||
0, /* thread_state_offset */
|
||||
0, /* thread_priority_offset */
|
||||
0, /* thread_prev_offset */
|
||||
0, /* thread_next_offset */
|
||||
false, /* thread_offsets_updated */
|
||||
1, /* threadid_start */
|
||||
&rtos_uCOS_III_Cortex_M_stacking, /* stacking_info */
|
||||
0, /* num_threads */
|
||||
0, /* num_threads */
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -159,10 +159,10 @@ static int uCOS_III_find_last_thread_address(struct rtos *rtos, symbol_address_t
|
|||
symbol_address_t thread_list_address = 0;
|
||||
|
||||
retval = target_read_memory(rtos->target,
|
||||
rtos->symbols[uCOS_III_VAL_OSTaskDbgListPtr].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_list_address);
|
||||
rtos->symbols[uCOS_III_VAL_OSTaskDbgListPtr].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_list_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread list address");
|
||||
return retval;
|
||||
|
@ -173,10 +173,10 @@ static int uCOS_III_find_last_thread_address(struct rtos *rtos, symbol_address_t
|
|||
*thread_address = thread_list_address;
|
||||
|
||||
retval = target_read_memory(rtos->target,
|
||||
thread_list_address + params->thread_next_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_list_address);
|
||||
thread_list_address + params->thread_next_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_list_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read next thread address");
|
||||
return retval;
|
||||
|
@ -227,10 +227,10 @@ static int uCOS_III_update_thread_offsets(struct rtos *rtos)
|
|||
const struct thread_offset_map *thread_offset_map = &thread_offset_maps[i];
|
||||
|
||||
int retval = target_read_memory(rtos->target,
|
||||
rtos->symbols[thread_offset_map->symbol_value].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)thread_offset_map->thread_offset);
|
||||
rtos->symbols[thread_offset_map->symbol_value].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)thread_offset_map->thread_offset);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread offset");
|
||||
return retval;
|
||||
|
@ -244,7 +244,7 @@ static int uCOS_III_update_thread_offsets(struct rtos *rtos)
|
|||
static int uCOS_III_detect_rtos(struct target *target)
|
||||
{
|
||||
return target->rtos->symbols != NULL &&
|
||||
target->rtos->symbols[uCOS_III_VAL_OSRunning].address != 0;
|
||||
target->rtos->symbols[uCOS_III_VAL_OSRunning].address != 0;
|
||||
}
|
||||
|
||||
static int uCOS_III_reset_handler(struct target *target, enum target_reset_mode reset_mode, void *priv)
|
||||
|
@ -263,8 +263,7 @@ static int uCOS_III_create(struct target *target)
|
|||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(uCOS_III_params_list); i++)
|
||||
if (strcmp(uCOS_III_params_list[i].target_name, target->type->name) == 0) {
|
||||
params = malloc(sizeof(*params) +
|
||||
UCOS_III_MAX_THREADS * sizeof(*params->threads));
|
||||
params = malloc(sizeof(*params) + (UCOS_III_MAX_THREADS * sizeof(*params->threads)));
|
||||
if (params == NULL) {
|
||||
LOG_ERROR("uCOS-III: out of memory");
|
||||
return ERROR_FAIL;
|
||||
|
@ -294,13 +293,18 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
uint8_t rtos_running;
|
||||
|
||||
retval = target_read_u8(rtos->target,
|
||||
rtos->symbols[uCOS_III_VAL_OSRunning].address,
|
||||
&rtos_running);
|
||||
rtos->symbols[uCOS_III_VAL_OSRunning].address,
|
||||
&rtos_running);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read RTOS running");
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (rtos_running != 1 && rtos_running != 0) {
|
||||
LOG_ERROR("uCOS-III: invalid RTOS running value");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
if (!rtos_running) {
|
||||
rtos->thread_details = calloc(1, sizeof(struct thread_detail));
|
||||
if (rtos->thread_details == NULL) {
|
||||
|
@ -327,10 +331,10 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
symbol_address_t current_thread_address = 0;
|
||||
|
||||
retval = target_read_memory(rtos->target,
|
||||
rtos->symbols[uCOS_III_VAL_OSTCBCurPtr].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)¤t_thread_address);
|
||||
rtos->symbols[uCOS_III_VAL_OSTCBCurPtr].address,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)¤t_thread_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read current thread address");
|
||||
return retval;
|
||||
|
@ -338,8 +342,8 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
|
||||
/* read number of tasks */
|
||||
retval = target_read_u16(rtos->target,
|
||||
rtos->symbols[uCOS_III_VAL_OSTaskQty].address,
|
||||
(void *)&rtos->thread_count);
|
||||
rtos->symbols[uCOS_III_VAL_OSTaskQty].address,
|
||||
(void *)&rtos->thread_count);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread count");
|
||||
return retval;
|
||||
|
@ -368,9 +372,7 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
char thread_str_buffer[UCOS_III_MAX_STRLEN + 1];
|
||||
|
||||
/* find or create new threadid */
|
||||
retval = uCOS_III_find_or_create_thread(rtos,
|
||||
thread_address,
|
||||
&thread_detail->threadid);
|
||||
retval = uCOS_III_find_or_create_thread(rtos, thread_address, &thread_detail->threadid);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to find or create thread");
|
||||
return retval;
|
||||
|
@ -385,19 +387,19 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
symbol_address_t thread_name_address = 0;
|
||||
|
||||
retval = target_read_memory(rtos->target,
|
||||
thread_address + params->thread_name_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_name_address);
|
||||
thread_address + params->thread_name_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_name_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to name address");
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = target_read_buffer(rtos->target,
|
||||
thread_name_address,
|
||||
sizeof(thread_str_buffer),
|
||||
(void *)thread_str_buffer);
|
||||
thread_name_address,
|
||||
sizeof(thread_str_buffer),
|
||||
(void *)thread_str_buffer);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread name");
|
||||
return retval;
|
||||
|
@ -411,16 +413,16 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
uint8_t thread_priority;
|
||||
|
||||
retval = target_read_u8(rtos->target,
|
||||
thread_address + params->thread_state_offset,
|
||||
&thread_state);
|
||||
thread_address + params->thread_state_offset,
|
||||
&thread_state);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread state");
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = target_read_u8(rtos->target,
|
||||
thread_address + params->thread_priority_offset,
|
||||
&thread_priority);
|
||||
thread_address + params->thread_priority_offset,
|
||||
&thread_priority);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read thread priority");
|
||||
return retval;
|
||||
|
@ -434,15 +436,15 @@ static int uCOS_III_update_threads(struct rtos *rtos)
|
|||
thread_state_str = "Unknown";
|
||||
|
||||
snprintf(thread_str_buffer, sizeof(thread_str_buffer), "State: %s, Priority: %d",
|
||||
thread_state_str, thread_priority);
|
||||
thread_state_str, thread_priority);
|
||||
thread_detail->extra_info_str = strdup(thread_str_buffer);
|
||||
|
||||
/* read previous thread address */
|
||||
retval = target_read_memory(rtos->target,
|
||||
thread_address + params->thread_prev_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_address);
|
||||
thread_address + params->thread_prev_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&thread_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read previous thread address");
|
||||
return retval;
|
||||
|
@ -470,19 +472,19 @@ static int uCOS_III_get_thread_reg_list(struct rtos *rtos, threadid_t threadid,
|
|||
symbol_address_t stack_address = 0;
|
||||
|
||||
retval = target_read_memory(rtos->target,
|
||||
thread_address + params->thread_stack_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&stack_address);
|
||||
thread_address + params->thread_stack_offset,
|
||||
params->pointer_width,
|
||||
1,
|
||||
(void *)&stack_address);
|
||||
if (retval != ERROR_OK) {
|
||||
LOG_ERROR("uCOS-III: failed to read stack address");
|
||||
return retval;
|
||||
}
|
||||
|
||||
return rtos_generic_stack_read(rtos->target,
|
||||
params->stacking_info,
|
||||
stack_address,
|
||||
hex_reg_list);
|
||||
params->stacking_info,
|
||||
stack_address,
|
||||
hex_reg_list);
|
||||
}
|
||||
|
||||
static int uCOS_III_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
|
||||
|
|
|
@ -302,7 +302,7 @@ int add_service(char *name,
|
|||
|
||||
struct sockaddr_in addr_in;
|
||||
socklen_t addr_in_size = sizeof(addr_in);
|
||||
getsockname(c->fd, &addr_in, &addr_in_size);
|
||||
getsockname(c->fd, (struct sockaddr *)&addr_in, &addr_in_size);
|
||||
LOG_INFO("Listening on port %d for %s connections",
|
||||
ntohs(addr_in.sin_port), name);
|
||||
} else if (c->type == CONNECTION_STDINOUT) {
|
||||
|
|
|
@ -17,6 +17,7 @@ static uint32_t load(const struct target *target, unsigned int rd,
|
|||
return ld(rd, base, offset);
|
||||
}
|
||||
assert(0);
|
||||
return 0; // Silence -Werror=return-type
|
||||
}
|
||||
|
||||
static uint32_t store(const struct target *target, unsigned int src,
|
||||
|
@ -31,6 +32,7 @@ static uint32_t store(const struct target *target, unsigned int src,
|
|||
return sd(src, base, offset);
|
||||
}
|
||||
assert(0);
|
||||
return 0; // Silence -Werror=return-type
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -44,6 +44,13 @@ bool riscv_batch_full(struct riscv_batch *batch)
|
|||
|
||||
void riscv_batch_run(struct riscv_batch *batch)
|
||||
{
|
||||
if (batch->used_scans == 0) {
|
||||
LOG_DEBUG("Ignoring empty batch.");
|
||||
return;
|
||||
}
|
||||
|
||||
keep_alive();
|
||||
|
||||
LOG_DEBUG("running a batch of %ld scans", (long)batch->used_scans);
|
||||
riscv_batch_add_nop(batch);
|
||||
|
||||
|
@ -96,7 +103,7 @@ size_t riscv_batch_add_dmi_read(struct riscv_batch *batch, unsigned address)
|
|||
batch->read_keys[batch->read_keys_used] = batch->used_scans - 1;
|
||||
LOG_DEBUG("read key %u for batch 0x%p is %u (0x%p)",
|
||||
(unsigned) batch->read_keys_used, batch, (unsigned) (batch->used_scans - 1),
|
||||
(uint64_t*)batch->data_in + (batch->used_scans + 1));
|
||||
batch->data_in + sizeof(uint64_t) * (batch->used_scans + 1));
|
||||
return batch->read_keys_used++;
|
||||
}
|
||||
|
||||
|
@ -105,8 +112,15 @@ uint64_t riscv_batch_get_dmi_read(struct riscv_batch *batch, size_t key)
|
|||
assert(key < batch->read_keys_used);
|
||||
size_t index = batch->read_keys[key];
|
||||
assert(index <= batch->used_scans);
|
||||
uint64_t *addr = ((uint64_t *)(batch->data_in) + index);
|
||||
return *addr;
|
||||
uint8_t *base = batch->data_in + 8 * index;
|
||||
return base[0] |
|
||||
((uint64_t) base[1]) << 8 |
|
||||
((uint64_t) base[2]) << 16 |
|
||||
((uint64_t) base[3]) << 24 |
|
||||
((uint64_t) base[4]) << 32 |
|
||||
((uint64_t) base[5]) << 40 |
|
||||
((uint64_t) base[6]) << 48 |
|
||||
((uint64_t) base[7]) << 56;
|
||||
}
|
||||
|
||||
void riscv_batch_add_nop(struct riscv_batch *batch)
|
||||
|
|
|
@ -23,8 +23,8 @@ struct riscv_batch {
|
|||
|
||||
size_t idle_count;
|
||||
|
||||
char *data_out;
|
||||
char *data_in;
|
||||
uint8_t *data_out;
|
||||
uint8_t *data_in;
|
||||
struct scan_field *fields;
|
||||
|
||||
/* In JTAG we scan out the previous value's output when performing a
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,8 @@
|
|||
#ifndef TARGET__RISCV__GDB_REGS_H
|
||||
#define TARGET__RISCV__GDB_REGS_H
|
||||
|
||||
// gdb's register list is defined in riscv_gdb_reg_names gdb/riscv-tdep.c in
|
||||
// its source tree. We must interpret the numbers the same here.
|
||||
enum gdb_regno {
|
||||
GDB_REGNO_XPR0 = 0,
|
||||
GDB_REGNO_X0 = GDB_REGNO_XPR0 + 0,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#endif
|
||||
|
||||
#include "target/target.h"
|
||||
#include "target/register.h"
|
||||
#include "riscv.h"
|
||||
#include "program.h"
|
||||
#include "helper/log.h"
|
||||
|
@ -41,6 +42,8 @@ int riscv_program_init(struct riscv_program *p, struct target *target)
|
|||
|
||||
int riscv_program_exec(struct riscv_program *p, struct target *t)
|
||||
{
|
||||
keep_alive();
|
||||
|
||||
if (riscv_debug_buffer_leave(t, p) != ERROR_OK) {
|
||||
LOG_ERROR("unable to write program buffer exit code");
|
||||
return ERROR_FAIL;
|
||||
|
@ -367,7 +370,7 @@ int riscv_program_addi(struct riscv_program *p, enum gdb_regno d, enum gdb_regno
|
|||
return riscv_program_insert(p, addi(d, s, u));
|
||||
}
|
||||
|
||||
int riscv_program_fsd(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr)
|
||||
int riscv_program_fsx(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr)
|
||||
{
|
||||
assert(d >= GDB_REGNO_FPR0);
|
||||
assert(d <= GDB_REGNO_FPR31);
|
||||
|
@ -376,21 +379,43 @@ int riscv_program_fsd(struct riscv_program *p, enum gdb_regno d, riscv_addr_t ad
|
|||
: riscv_program_gettemp(p);
|
||||
if (riscv_program_lah(p, t, addr) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
if (riscv_program_insert(p, fsd(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr))) != ERROR_OK)
|
||||
uint32_t instruction;
|
||||
switch (p->target->reg_cache->reg_list[GDB_REGNO_FPR0].size) {
|
||||
case 64:
|
||||
instruction = fsd(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr));
|
||||
break;
|
||||
case 32:
|
||||
instruction = fsw(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr));
|
||||
break;
|
||||
default:
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
if (riscv_program_insert(p, instruction) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
riscv_program_puttemp(p, t);
|
||||
p->writes_memory = true;
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
int riscv_program_fld(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr)
|
||||
int riscv_program_flx(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr)
|
||||
{
|
||||
assert(d >= GDB_REGNO_FPR0);
|
||||
assert(d <= GDB_REGNO_FPR31);
|
||||
enum gdb_regno t = riscv_program_gah(p, addr) == 0 ? GDB_REGNO_X0 : d;
|
||||
if (riscv_program_lah(p, t, addr) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
if (riscv_program_insert(p, fld(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr))) != ERROR_OK)
|
||||
uint32_t instruction;
|
||||
switch (p->target->reg_cache->reg_list[GDB_REGNO_FPR0].size) {
|
||||
case 64:
|
||||
instruction = fld(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr));
|
||||
break;
|
||||
case 32:
|
||||
instruction = flw(d - GDB_REGNO_FPR0, t, riscv_program_gal(p, addr));
|
||||
break;
|
||||
default:
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
if (riscv_program_insert(p, instruction) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
@ -453,7 +478,11 @@ riscv_addr_t riscv_program_gah(struct riscv_program *p, riscv_addr_t addr)
|
|||
|
||||
riscv_addr_t riscv_program_gal(struct riscv_program *p, riscv_addr_t addr)
|
||||
{
|
||||
return ((addr > 0) ? 1 : 0) * (abs(addr) & 0x7FF);
|
||||
if (addr > 0) {
|
||||
return (addr & 0x7FF);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int riscv_program_lah(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr)
|
||||
|
|
|
@ -114,8 +114,8 @@ int riscv_program_ebreak(struct riscv_program *p);
|
|||
int riscv_program_lui(struct riscv_program *p, enum gdb_regno d, int32_t u);
|
||||
int riscv_program_addi(struct riscv_program *p, enum gdb_regno d, enum gdb_regno s, int16_t i);
|
||||
|
||||
int riscv_program_fsd(struct riscv_program *p, enum gdb_regno s, riscv_addr_t addr);
|
||||
int riscv_program_fld(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr);
|
||||
int riscv_program_fsx(struct riscv_program *p, enum gdb_regno s, riscv_addr_t addr);
|
||||
int riscv_program_flx(struct riscv_program *p, enum gdb_regno d, riscv_addr_t addr);
|
||||
|
||||
/* Assembler macros. */
|
||||
int riscv_program_li(struct riscv_program *p, enum gdb_regno d, riscv_reg_t c);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "helper/time_support.h"
|
||||
#include "riscv.h"
|
||||
#include "asm.h"
|
||||
#include "gdb_regs.h"
|
||||
|
||||
/**
|
||||
* Since almost everything can be accomplish by scanning the dbus register, all
|
||||
|
@ -155,21 +156,6 @@ typedef enum slot {
|
|||
|
||||
#define DBUS_ADDRESS_UNKNOWN 0xffff
|
||||
|
||||
// gdb's register list is defined in riscv_gdb_reg_names gdb/riscv-tdep.c in
|
||||
// its source tree. We must interpret the numbers the same here.
|
||||
enum {
|
||||
REG_XPR0 = 0,
|
||||
REG_XPR31 = 31,
|
||||
REG_PC = 32,
|
||||
REG_FPR0 = 33,
|
||||
REG_FPR31 = 64,
|
||||
REG_CSR0 = 65,
|
||||
REG_MSTATUS = CSR_MSTATUS + REG_CSR0,
|
||||
REG_CSR4095 = 4160,
|
||||
REG_PRIV = 4161,
|
||||
REG_COUNT
|
||||
};
|
||||
|
||||
#define DRAM_CACHE_SIZE 16
|
||||
|
||||
struct trigger {
|
||||
|
@ -269,6 +255,7 @@ static unsigned int slot_offset(const struct target *target, slot_t slot)
|
|||
LOG_ERROR("slot_offset called with xlen=%d, slot=%d",
|
||||
riscv_xlen(target), slot);
|
||||
assert(0);
|
||||
return 0; // Silence -Werror=return-type
|
||||
}
|
||||
|
||||
static uint32_t load_slot(const struct target *target, unsigned int dest,
|
||||
|
@ -1117,7 +1104,7 @@ static int execute_resume(struct target *target, bool step)
|
|||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
struct reg *mstatus_reg = &target->reg_cache->reg_list[REG_MSTATUS];
|
||||
struct reg *mstatus_reg = &target->reg_cache->reg_list[GDB_REGNO_MSTATUS];
|
||||
if (mstatus_reg->valid) {
|
||||
uint64_t mstatus_user = buf_get_u64(mstatus_reg->value, 0, riscv_xlen(target));
|
||||
if (mstatus_user != info->mstatus_actual) {
|
||||
|
@ -1201,15 +1188,15 @@ static void update_reg_list(struct target *target)
|
|||
if (info->reg_values) {
|
||||
free(info->reg_values);
|
||||
}
|
||||
info->reg_values = malloc(REG_COUNT * riscv_xlen(target) / 4);
|
||||
info->reg_values = malloc(GDB_REGNO_COUNT * riscv_xlen(target) / 4);
|
||||
|
||||
for (unsigned int i = 0; i < REG_COUNT; i++) {
|
||||
for (unsigned int i = 0; i < GDB_REGNO_COUNT; i++) {
|
||||
struct reg *r = &target->reg_cache->reg_list[i];
|
||||
r->value = info->reg_values + i * riscv_xlen(target) / 4;
|
||||
if (r->dirty) {
|
||||
LOG_ERROR("Register %d was dirty. Its value is lost.", i);
|
||||
}
|
||||
if (i == REG_PRIV) {
|
||||
if (i == GDB_REGNO_PRIV) {
|
||||
r->size = 8;
|
||||
} else {
|
||||
r->size = riscv_xlen(target);
|
||||
|
@ -1241,7 +1228,7 @@ static void reg_cache_set(struct target *target, unsigned int number,
|
|||
|
||||
static int update_mstatus_actual(struct target *target)
|
||||
{
|
||||
struct reg *mstatus_reg = &target->reg_cache->reg_list[REG_MSTATUS];
|
||||
struct reg *mstatus_reg = &target->reg_cache->reg_list[GDB_REGNO_MSTATUS];
|
||||
if (mstatus_reg->valid) {
|
||||
// We previously made it valid.
|
||||
return ERROR_OK;
|
||||
|
@ -1249,7 +1236,7 @@ static int update_mstatus_actual(struct target *target)
|
|||
|
||||
// Force reading the register. In that process mstatus_actual will be
|
||||
// updated.
|
||||
return register_get(&target->reg_cache->reg_list[REG_MSTATUS]);
|
||||
return register_get(&target->reg_cache->reg_list[GDB_REGNO_MSTATUS]);
|
||||
}
|
||||
|
||||
/*** OpenOCD target functions. ***/
|
||||
|
@ -1257,8 +1244,8 @@ static int update_mstatus_actual(struct target *target)
|
|||
static int register_read(struct target *target, riscv_reg_t *value, int regnum)
|
||||
{
|
||||
riscv011_info_t *info = get_info(target);
|
||||
if (regnum >= REG_CSR0 && regnum <= REG_CSR4095) {
|
||||
cache_set32(target, 0, csrr(S0, regnum - REG_CSR0));
|
||||
if (regnum >= GDB_REGNO_CSR0 && regnum <= GDB_REGNO_CSR4095) {
|
||||
cache_set32(target, 0, csrr(S0, regnum - GDB_REGNO_CSR0));
|
||||
cache_set_store(target, 1, S0, SLOT0);
|
||||
cache_set_jump(target, 2);
|
||||
} else {
|
||||
|
@ -1281,7 +1268,7 @@ static int register_read(struct target *target, riscv_reg_t *value, int regnum)
|
|||
*value = cache_get(target, SLOT0);
|
||||
LOG_DEBUG("reg[%d]=0x%" PRIx64, regnum, *value);
|
||||
|
||||
if (regnum == REG_MSTATUS) {
|
||||
if (regnum == GDB_REGNO_MSTATUS) {
|
||||
info->mstatus_actual = *value;
|
||||
}
|
||||
|
||||
|
@ -1296,13 +1283,13 @@ static int register_get(struct reg *reg)
|
|||
maybe_write_tselect(target);
|
||||
riscv_reg_t value = ~0;
|
||||
|
||||
if (reg->number <= REG_XPR31) {
|
||||
if (reg->number <= GDB_REGNO_XPR31) {
|
||||
value = reg_cache_get(target, reg->number);
|
||||
LOG_DEBUG("%s=0x%" PRIx64, reg->name, reg_cache_get(target, reg->number));
|
||||
} else if (reg->number == REG_PC) {
|
||||
} else if (reg->number == GDB_REGNO_PC) {
|
||||
value = info->dpc;
|
||||
LOG_DEBUG("%s=0x%" PRIx64 " (cached)", reg->name, info->dpc);
|
||||
} else if (reg->number >= REG_FPR0 && reg->number <= REG_FPR31) {
|
||||
} else if (reg->number >= GDB_REGNO_FPR0 && reg->number <= GDB_REGNO_FPR31) {
|
||||
int result = update_mstatus_actual(target);
|
||||
if (result != ERROR_OK) {
|
||||
return result;
|
||||
|
@ -1316,22 +1303,24 @@ static int register_get(struct reg *reg)
|
|||
}
|
||||
|
||||
if (riscv_xlen(target) == 32) {
|
||||
cache_set32(target, i++, fsw(reg->number - REG_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
cache_set32(target, i++, fsw(reg->number - GDB_REGNO_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
} else {
|
||||
cache_set32(target, i++, fsd(reg->number - REG_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
cache_set32(target, i++, fsd(reg->number - GDB_REGNO_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
}
|
||||
cache_set_jump(target, i++);
|
||||
|
||||
if (cache_write(target, 4, true) != ERROR_OK) {
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
} else if (reg->number == GDB_REGNO_PRIV) {
|
||||
value = get_field(info->dcsr, DCSR_PRV);
|
||||
} else {
|
||||
if (register_read(target, &value, reg->number) != ERROR_OK)
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
buf_set_u64(reg->value, 0, riscv_xlen(target), value);
|
||||
|
||||
if (reg->number == REG_MSTATUS) {
|
||||
if (reg->number == GDB_REGNO_MSTATUS) {
|
||||
reg->valid = true;
|
||||
}
|
||||
|
||||
|
@ -1354,13 +1343,13 @@ static int register_write(struct target *target, unsigned int number,
|
|||
cache_set_load(target, 0, S0, SLOT0);
|
||||
cache_set_store(target, 1, S0, SLOT_LAST);
|
||||
cache_set_jump(target, 2);
|
||||
} else if (number <= REG_XPR31) {
|
||||
cache_set_load(target, 0, number - REG_XPR0, SLOT0);
|
||||
} else if (number <= GDB_REGNO_XPR31) {
|
||||
cache_set_load(target, 0, number - GDB_REGNO_XPR0, SLOT0);
|
||||
cache_set_jump(target, 1);
|
||||
} else if (number == REG_PC) {
|
||||
} else if (number == GDB_REGNO_PC) {
|
||||
info->dpc = value;
|
||||
return ERROR_OK;
|
||||
} else if (number >= REG_FPR0 && number <= REG_FPR31) {
|
||||
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
||||
int result = update_mstatus_actual(target);
|
||||
if (result != ERROR_OK) {
|
||||
return result;
|
||||
|
@ -1374,20 +1363,20 @@ static int register_write(struct target *target, unsigned int number,
|
|||
}
|
||||
|
||||
if (riscv_xlen(target) == 32) {
|
||||
cache_set32(target, i++, flw(number - REG_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
cache_set32(target, i++, flw(number - GDB_REGNO_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
} else {
|
||||
cache_set32(target, i++, fld(number - REG_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
cache_set32(target, i++, fld(number - GDB_REGNO_FPR0, 0, DEBUG_RAM_START + 16));
|
||||
}
|
||||
cache_set_jump(target, i++);
|
||||
} else if (number >= REG_CSR0 && number <= REG_CSR4095) {
|
||||
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
||||
cache_set_load(target, 0, S0, SLOT0);
|
||||
cache_set32(target, 1, csrw(S0, number - REG_CSR0));
|
||||
cache_set32(target, 1, csrw(S0, number - GDB_REGNO_CSR0));
|
||||
cache_set_jump(target, 2);
|
||||
|
||||
if (number == REG_MSTATUS) {
|
||||
if (number == GDB_REGNO_MSTATUS) {
|
||||
info->mstatus_actual = value;
|
||||
}
|
||||
} else if (number == REG_PRIV) {
|
||||
} else if (number == GDB_REGNO_PRIV) {
|
||||
info->dcsr = set_field(info->dcsr, DCSR_PRV, value);
|
||||
return ERROR_OK;
|
||||
} else {
|
||||
|
@ -1481,16 +1470,16 @@ static int init_target(struct command_context *cmd_ctx,
|
|||
|
||||
target->reg_cache = calloc(1, sizeof(*target->reg_cache));
|
||||
target->reg_cache->name = "RISC-V registers";
|
||||
target->reg_cache->num_regs = REG_COUNT;
|
||||
target->reg_cache->num_regs = GDB_REGNO_COUNT;
|
||||
|
||||
target->reg_cache->reg_list = calloc(REG_COUNT, sizeof(struct reg));
|
||||
target->reg_cache->reg_list = calloc(GDB_REGNO_COUNT, sizeof(struct reg));
|
||||
|
||||
const unsigned int max_reg_name_len = 12;
|
||||
info->reg_names = calloc(1, REG_COUNT * max_reg_name_len);
|
||||
info->reg_names = calloc(1, GDB_REGNO_COUNT * max_reg_name_len);
|
||||
char *reg_name = info->reg_names;
|
||||
info->reg_values = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < REG_COUNT; i++) {
|
||||
for (unsigned int i = 0; i < GDB_REGNO_COUNT; i++) {
|
||||
struct reg *r = &target->reg_cache->reg_list[i];
|
||||
r->number = i;
|
||||
r->caller_save = true;
|
||||
|
@ -1499,22 +1488,22 @@ static int init_target(struct command_context *cmd_ctx,
|
|||
r->exist = true;
|
||||
r->type = &riscv_reg_arch_type;
|
||||
r->arch_info = target;
|
||||
if (i <= REG_XPR31) {
|
||||
if (i <= GDB_REGNO_XPR31) {
|
||||
sprintf(reg_name, "x%d", i);
|
||||
} else if (i == REG_PC) {
|
||||
} else if (i == GDB_REGNO_PC) {
|
||||
sprintf(reg_name, "pc");
|
||||
} else if (i >= REG_FPR0 && i <= REG_FPR31) {
|
||||
sprintf(reg_name, "f%d", i - REG_FPR0);
|
||||
} else if (i >= REG_CSR0 && i <= REG_CSR4095) {
|
||||
sprintf(reg_name, "csr%d", i - REG_CSR0);
|
||||
} else if (i == REG_PRIV) {
|
||||
} else if (i >= GDB_REGNO_FPR0 && i <= GDB_REGNO_FPR31) {
|
||||
sprintf(reg_name, "f%d", i - GDB_REGNO_FPR0);
|
||||
} else if (i >= GDB_REGNO_CSR0 && i <= GDB_REGNO_CSR4095) {
|
||||
sprintf(reg_name, "csr%d", i - GDB_REGNO_CSR0);
|
||||
} else if (i == GDB_REGNO_PRIV) {
|
||||
sprintf(reg_name, "priv");
|
||||
}
|
||||
if (reg_name[0]) {
|
||||
r->name = reg_name;
|
||||
}
|
||||
reg_name += strlen(reg_name) + 1;
|
||||
assert(reg_name < info->reg_names + REG_COUNT * max_reg_name_len);
|
||||
assert(reg_name < info->reg_names + GDB_REGNO_COUNT * max_reg_name_len);
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
|
@ -1579,7 +1568,7 @@ static int step(struct target *target, int current, target_addr_t address,
|
|||
LOG_WARNING("Asked to resume at 32-bit PC on %d-bit target.",
|
||||
riscv_xlen(target));
|
||||
}
|
||||
int result = register_write(target, REG_PC, address);
|
||||
int result = register_write(target, GDB_REGNO_PC, address);
|
||||
if (result != ERROR_OK)
|
||||
return result;
|
||||
}
|
||||
|
@ -2006,7 +1995,7 @@ static int riscv011_resume(struct target *target, int current,
|
|||
LOG_WARNING("Asked to resume at 32-bit PC on %d-bit target.",
|
||||
riscv_xlen(target));
|
||||
}
|
||||
int result = register_write(target, REG_PC, address);
|
||||
int result = register_write(target, GDB_REGNO_PC, address);
|
||||
if (result != ERROR_OK)
|
||||
return result;
|
||||
}
|
||||
|
@ -2091,7 +2080,7 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
cache_write(target, CACHE_NO_READ, false);
|
||||
|
||||
riscv011_info_t *info = get_info(target);
|
||||
const int max_batch_size = 256;
|
||||
const unsigned max_batch_size = 256;
|
||||
scans_t *scans = scans_new(target, max_batch_size);
|
||||
|
||||
uint32_t result_value = 0x777;
|
||||
|
@ -2252,7 +2241,7 @@ static int write_memory(struct target *target, target_addr_t address,
|
|||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
const int max_batch_size = 256;
|
||||
const unsigned max_batch_size = 256;
|
||||
scans_t *scans = scans_new(target, max_batch_size);
|
||||
|
||||
uint32_t result_value = 0x777;
|
||||
|
|
|
@ -63,7 +63,6 @@ static void riscv013_fill_dmi_write_u64(struct target *target, char *buf, int a,
|
|||
static void riscv013_fill_dmi_read_u64(struct target *target, char *buf, int a);
|
||||
static int riscv013_dmi_write_u64_bits(struct target *target);
|
||||
static void riscv013_fill_dmi_nop_u64(struct target *target, char *buf);
|
||||
static void riscv013_reset_current_hart(struct target *target);
|
||||
static int riscv013_test_compliance(struct target *target);
|
||||
|
||||
/**
|
||||
|
@ -218,7 +217,7 @@ static void decode_dmi(char *text, unsigned address, unsigned data)
|
|||
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYHALTED, "anyhalted" },
|
||||
{ DMI_DMSTATUS, DMI_DMSTATUS_AUTHENTICATED, "authenticated" },
|
||||
{ DMI_DMSTATUS, DMI_DMSTATUS_AUTHBUSY, "authbusy" },
|
||||
{ DMI_DMSTATUS, DMI_DMSTATUS_CFGSTRVALID, "cfgstrvalid" },
|
||||
{ DMI_DMSTATUS, DMI_DMSTATUS_DEVTREEVALID, "devtreevalid" },
|
||||
{ DMI_DMSTATUS, DMI_DMSTATUS_VERSION, "version" },
|
||||
|
||||
{ DMI_ABSTRACTCS, DMI_ABSTRACTCS_PROGSIZE, "progsize" },
|
||||
|
@ -365,17 +364,6 @@ static void increase_dmi_busy_delay(struct target *target)
|
|||
dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
|
||||
}
|
||||
|
||||
static void increase_ac_busy_delay(struct target *target)
|
||||
{
|
||||
riscv013_info_t *info = get_info(target);
|
||||
info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
|
||||
LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
||||
info->dtmcontrol_idle, info->dmi_busy_delay,
|
||||
info->ac_busy_delay);
|
||||
|
||||
dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* exec: If this is set, assume the scan results in an execution, so more
|
||||
* run-test/idle cycles may be required.
|
||||
|
@ -433,7 +421,6 @@ static uint64_t dmi_read(struct target *target, uint16_t address)
|
|||
{
|
||||
select_dmi(target);
|
||||
|
||||
uint64_t value;
|
||||
dmi_status_t status;
|
||||
uint16_t address_in;
|
||||
|
||||
|
@ -441,7 +428,7 @@ static uint64_t dmi_read(struct target *target, uint16_t address)
|
|||
|
||||
// This first loop ensures that the read request was actually sent
|
||||
// to the target. Note that if for some reason this stays busy,
|
||||
// it is actually due to the Previous dmi_read or dmi_write.
|
||||
// it is actually due to the previous dmi_read or dmi_write.
|
||||
for (i = 0; i < 256; i++) {
|
||||
status = dmi_scan(target, NULL, NULL, DMI_OP_READ, address, 0,
|
||||
false);
|
||||
|
@ -456,14 +443,14 @@ static uint64_t dmi_read(struct target *target, uint16_t address)
|
|||
}
|
||||
|
||||
if (status != DMI_STATUS_SUCCESS) {
|
||||
LOG_ERROR("Failed read from 0x%x" PRIx64 ", status=%d",
|
||||
address, status);
|
||||
LOG_ERROR("Failed read from 0x%x; status=%d", address, status);
|
||||
abort();
|
||||
}
|
||||
|
||||
// This second loop ensures that we got the read
|
||||
// data back. Note that NOP can result in a 'busy' result as well, but
|
||||
// that would be noticed on the next DMI access we do.
|
||||
uint64_t value;
|
||||
for (i = 0; i < 256; i++) {
|
||||
status = dmi_scan(target, &address_in, &value, DMI_OP_NOP, address, 0,
|
||||
false);
|
||||
|
@ -472,7 +459,7 @@ static uint64_t dmi_read(struct target *target, uint16_t address)
|
|||
} else if (status == DMI_STATUS_SUCCESS) {
|
||||
break;
|
||||
} else {
|
||||
LOG_ERROR("failed read (NOP) at 0x%x, status=%d\n", address, status);
|
||||
LOG_ERROR("failed read (NOP) at 0x%x, status=%d", address, status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -501,13 +488,13 @@ static void dmi_write(struct target *target, uint16_t address, uint64_t value)
|
|||
} else if (status == DMI_STATUS_SUCCESS) {
|
||||
break;
|
||||
} else {
|
||||
LOG_ERROR("failed write to 0x%x, status=%d\n", address, status);
|
||||
LOG_ERROR("failed write to 0x%x, status=%d", address, status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (status != DMI_STATUS_SUCCESS) {
|
||||
LOG_ERROR("Failed write to 0x%x;, status=%d\n",
|
||||
LOG_ERROR("Failed write to 0x%x;, status=%d",
|
||||
address, status);
|
||||
abort();
|
||||
}
|
||||
|
@ -522,16 +509,25 @@ static void dmi_write(struct target *target, uint16_t address, uint64_t value)
|
|||
} else if (status == DMI_STATUS_SUCCESS) {
|
||||
break;
|
||||
} else {
|
||||
LOG_ERROR("failed write (NOP) at 0x%x, status=%d\n", address, status);
|
||||
LOG_ERROR("failed write (NOP) at 0x%x, status=%d", address, status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (status != DMI_STATUS_SUCCESS) {
|
||||
LOG_ERROR("failed to write (NOP) 0x%" PRIx64 " to 0x%x; status=%d\n", value, address, status);
|
||||
LOG_ERROR("failed to write (NOP) 0x%" PRIx64 " to 0x%x; status=%d", value, address, status);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static void increase_ac_busy_delay(struct target *target)
|
||||
{
|
||||
riscv013_info_t *info = get_info(target);
|
||||
info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
|
||||
LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
||||
info->dtmcontrol_idle, info->dmi_busy_delay,
|
||||
info->ac_busy_delay);
|
||||
}
|
||||
|
||||
uint32_t abstract_register_size(unsigned width)
|
||||
{
|
||||
switch (width) {
|
||||
|
@ -577,7 +573,7 @@ static int wait_for_idle(struct target *target, uint32_t *abstractcs)
|
|||
errors[info->cmderr], *abstractcs);
|
||||
}
|
||||
|
||||
LOG_ERROR("Timed out after %ds waiting for busy to go low. (abstractcs=0x%x)"
|
||||
LOG_ERROR("Timed out after %ds waiting for busy to go low (abstractcs=0x%x). "
|
||||
"Increase the timeout with riscv set_command_timeout_sec.",
|
||||
riscv_command_timeout_sec,
|
||||
*abstractcs);
|
||||
|
@ -783,7 +779,7 @@ static int register_write_direct(struct target *target, unsigned number,
|
|||
if (number <= GDB_REGNO_XPR31) {
|
||||
riscv_program_lx(&program, number, input);
|
||||
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
||||
riscv_program_fld(&program, number, input);
|
||||
riscv_program_flx(&program, number, input);
|
||||
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
||||
enum gdb_regno temp = riscv_program_gettemp(&program);
|
||||
riscv_program_lx(&program, temp, input);
|
||||
|
@ -819,7 +815,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t
|
|||
if (number <= GDB_REGNO_XPR31) {
|
||||
riscv_program_sx(&program, number, output);
|
||||
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
||||
riscv_program_fsd(&program, number, output);
|
||||
riscv_program_fsx(&program, number, output);
|
||||
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
||||
LOG_DEBUG("reading CSR index=0x%03x", number - GDB_REGNO_CSR0);
|
||||
enum gdb_regno temp = riscv_program_gettemp(&program);
|
||||
|
@ -908,7 +904,6 @@ static int init_target(struct command_context *cmd_ctx,
|
|||
generic_info->fill_dmi_read_u64 = &riscv013_fill_dmi_read_u64;
|
||||
generic_info->fill_dmi_nop_u64 = &riscv013_fill_dmi_nop_u64;
|
||||
generic_info->dmi_write_u64_bits = &riscv013_dmi_write_u64_bits;
|
||||
generic_info->reset_current_hart = &riscv013_reset_current_hart;
|
||||
generic_info->test_compliance = &riscv013_test_compliance;
|
||||
generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
|
||||
if (!generic_info->version_specific)
|
||||
|
@ -1148,7 +1143,7 @@ static int examine(struct target *target)
|
|||
r->xlen[i], r->debug_buffer_addr[i]);
|
||||
|
||||
if (riscv_program_gah(&program64, r->debug_buffer_addr[i])) {
|
||||
LOG_ERROR("This implementation will not work with hart %d with debug_buffer_addr of 0x%lx\n", i,
|
||||
LOG_ERROR("This implementation will not work with hart %d with debug_buffer_addr of 0x%lx", i,
|
||||
(long)r->debug_buffer_addr[i]);
|
||||
abort();
|
||||
}
|
||||
|
@ -1179,32 +1174,67 @@ static int examine(struct target *target)
|
|||
LOG_INFO("Examined RISC-V core; found %d harts",
|
||||
riscv_count_harts(target));
|
||||
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
||||
LOG_INFO(" hart %d: XLEN=%d, program buffer at 0x%" PRIx64
|
||||
", %d triggers", i, r->xlen[i], r->debug_buffer_addr[i],
|
||||
r->trigger_count[i]);
|
||||
if (riscv_hart_enabled(target, i)) {
|
||||
LOG_INFO(" hart %d: XLEN=%d, program buffer at 0x%" PRIx64
|
||||
", %d triggers", i, r->xlen[i], r->debug_buffer_addr[i],
|
||||
r->trigger_count[i]);
|
||||
} else {
|
||||
LOG_INFO(" hart %d: currently disabled", i);
|
||||
}
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static int assert_reset(struct target *target)
|
||||
{
|
||||
/*FIXME -- this only works for single-hart.*/
|
||||
RISCV_INFO(r);
|
||||
assert(r->current_hartid == 0);
|
||||
|
||||
select_dmi(target);
|
||||
LOG_DEBUG("ASSERTING NDRESET");
|
||||
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
||||
if (target->reset_halt) {
|
||||
LOG_DEBUG("TARGET RESET HALT SET, ensuring halt is set during reset.");
|
||||
control |= DMI_DMCONTROL_HALTREQ;
|
||||
|
||||
uint32_t control_base = set_field(0, DMI_DMCONTROL_DMACTIVE, 1);
|
||||
|
||||
if (target->rtos) {
|
||||
// There's only one target, and OpenOCD thinks each hart is a thread.
|
||||
// We must reset them all.
|
||||
|
||||
// TODO: Try to use hasel in dmcontrol
|
||||
|
||||
// Set haltreq/resumereq for each hart.
|
||||
uint32_t control = control_base;
|
||||
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
||||
if (!riscv_hart_enabled(target, i))
|
||||
continue;
|
||||
|
||||
control = set_field(control_base, DMI_DMCONTROL_HARTSEL, i);
|
||||
control = set_field(control, DMI_DMCONTROL_HALTREQ,
|
||||
target->reset_halt ? 1 : 0);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
}
|
||||
// Assert ndmreset
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("TARGET RESET HALT NOT SET");
|
||||
control &= ~DMI_DMCONTROL_HALTREQ;
|
||||
// Reset just this hart.
|
||||
uint32_t control = set_field(control_base, DMI_DMCONTROL_HARTSEL,
|
||||
r->current_hartid);
|
||||
control = set_field(control, DMI_DMCONTROL_HALTREQ,
|
||||
target->reset_halt ? 1 : 0);
|
||||
control = set_field(control, DMI_DMCONTROL_HARTRESET, 1);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
// Read back to check if hartreset is supported.
|
||||
uint32_t rb = dmi_read(target, DMI_DMCONTROL);
|
||||
if (!get_field(rb, DMI_DMCONTROL_HARTRESET)) {
|
||||
// Use ndmreset instead. That will reset the entire device, but
|
||||
// that's probably what OpenOCD wants anyway.
|
||||
control = set_field(control, DMI_DMCONTROL_HARTRESET, 0);
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
}
|
||||
}
|
||||
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
target->state = TARGET_RESET;
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
@ -1215,43 +1245,85 @@ static int deassert_reset(struct target *target)
|
|||
RISCV013_INFO(info);
|
||||
select_dmi(target);
|
||||
|
||||
/*FIXME -- this only works for Single Hart*/
|
||||
assert(r->current_hartid == 0);
|
||||
|
||||
/*FIXME -- is there bookkeeping we need to do here*/
|
||||
|
||||
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
||||
LOG_DEBUG("%d", r->current_hartid);
|
||||
|
||||
// Clear the reset, but make sure haltreq is still set
|
||||
if (target->reset_halt) {
|
||||
control |= DMI_DMCONTROL_HALTREQ;
|
||||
}
|
||||
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 0);
|
||||
uint32_t control = 0;
|
||||
control = set_field(control, DMI_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0);
|
||||
control = set_field(control, DMI_DMCONTROL_HARTSEL, r->current_hartid);
|
||||
control = set_field(control, DMI_DMCONTROL_DMACTIVE, 1);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
uint32_t status;
|
||||
uint32_t dmstatus;
|
||||
int dmi_busy_delay = info->dmi_busy_delay;
|
||||
time_t start = time(NULL);
|
||||
|
||||
if (target->reset_halt) {
|
||||
LOG_DEBUG("DEASSERTING RESET, waiting for hart to be halted.");
|
||||
LOG_DEBUG("Waiting for hart to be halted.");
|
||||
do {
|
||||
status = dmi_read(target, DMI_DMSTATUS);
|
||||
} while (get_field(status, DMI_DMSTATUS_ALLHALTED) == 0);
|
||||
} else {
|
||||
LOG_DEBUG("DEASSERTING RESET, waiting for hart to be running.");
|
||||
do {
|
||||
status = dmi_read(target, DMI_DMSTATUS);
|
||||
if (get_field(status, DMI_DMSTATUS_ANYHALTED) ||
|
||||
get_field(status, DMI_DMSTATUS_ANYUNAVAIL)) {
|
||||
LOG_ERROR("Unexpected hart status during reset.");
|
||||
abort();
|
||||
dmstatus = dmi_read(target, DMI_DMSTATUS);
|
||||
if (time(NULL) - start > riscv_reset_timeout_sec) {
|
||||
LOG_ERROR("Hart didn't halt coming out of reset in %ds; "
|
||||
"dmstatus=0x%x; "
|
||||
"Increase the timeout with riscv set_reset_timeout_sec.",
|
||||
riscv_reset_timeout_sec, dmstatus);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
} while (get_field(status, DMI_DMSTATUS_ALLRUNNING) == 0);
|
||||
target->state = TARGET_HALTED;
|
||||
} while (get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0);
|
||||
|
||||
control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
} else {
|
||||
LOG_DEBUG("Waiting for hart to be running.");
|
||||
do {
|
||||
dmstatus = dmi_read(target, DMI_DMSTATUS);
|
||||
if (get_field(dmstatus, DMI_DMSTATUS_ANYHALTED) ||
|
||||
get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
|
||||
LOG_ERROR("Unexpected hart status during reset. dmstatus=0x%x",
|
||||
dmstatus);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
if (time(NULL) - start > riscv_reset_timeout_sec) {
|
||||
LOG_ERROR("Hart didn't run coming out of reset in %ds; "
|
||||
"dmstatus=0x%x; "
|
||||
"Increase the timeout with riscv set_reset_timeout_sec.",
|
||||
riscv_reset_timeout_sec, dmstatus);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
} while (get_field(dmstatus, DMI_DMSTATUS_ALLRUNNING) == 0);
|
||||
target->state = TARGET_RUNNING;
|
||||
}
|
||||
info->dmi_busy_delay = dmi_busy_delay;
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
|
||||
{
|
||||
switch (size) {
|
||||
case 8:
|
||||
buffer[7] = value >> 56;
|
||||
buffer[6] = value >> 48;
|
||||
buffer[5] = value >> 40;
|
||||
buffer[4] = value >> 32;
|
||||
case 4:
|
||||
buffer[3] = value >> 24;
|
||||
buffer[2] = value >> 16;
|
||||
case 2:
|
||||
buffer[1] = value >> 8;
|
||||
case 1:
|
||||
buffer[0] = value;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the requested memory, taking care to execute every read exactly once,
|
||||
* even if cmderr=busy is encountered.
|
||||
*/
|
||||
static int read_memory(struct target *target, target_addr_t address,
|
||||
uint32_t size, uint32_t count, uint8_t *buffer)
|
||||
{
|
||||
|
@ -1277,7 +1349,6 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
riscv_addr_t r_addr = riscv_program_alloc_x(&program);
|
||||
riscv_program_fence(&program);
|
||||
riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
|
||||
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
||||
switch (size) {
|
||||
case 1:
|
||||
riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
||||
|
@ -1292,6 +1363,7 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
LOG_ERROR("Unsupported size: %d", size);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
||||
riscv_program_sw(&program, GDB_REGNO_S1, r_data);
|
||||
riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
|
||||
|
||||
|
@ -1299,9 +1371,9 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
* program execution mechanism. */
|
||||
switch (riscv_xlen(target)) {
|
||||
case 64:
|
||||
riscv_program_write_ram(&program, r_addr + 4, (((riscv_addr_t) address) - size) >> 32);
|
||||
riscv_program_write_ram(&program, r_addr + 4, ((riscv_addr_t) address) >> 32);
|
||||
case 32:
|
||||
riscv_program_write_ram(&program, r_addr, ((riscv_addr_t) address) - size);
|
||||
riscv_program_write_ram(&program, r_addr, (riscv_addr_t) address);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("unknown XLEN %d", riscv_xlen(target));
|
||||
|
@ -1318,26 +1390,8 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
uint32_t value = riscv_program_read_ram(&program, r_data);
|
||||
LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", address, value);
|
||||
switch (size) {
|
||||
case 1:
|
||||
buffer[0] = value;
|
||||
break;
|
||||
case 2:
|
||||
buffer[0] = value;
|
||||
buffer[1] = value >> 8;
|
||||
break;
|
||||
case 4:
|
||||
buffer[0] = value;
|
||||
buffer[1] = value >> 8;
|
||||
buffer[2] = value >> 16;
|
||||
buffer[3] = value >> 24;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("unsupported access size: %d", size);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
// Program has been executed once. d_addr contains address+size, and d_data
|
||||
// contains *address.
|
||||
|
||||
/* The rest of this program is designed to be fast so it reads various
|
||||
* DMI registers directly. */
|
||||
|
@ -1350,45 +1404,30 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
* case we need to back off a bit and try again. There's two
|
||||
* termination conditions to this loop: a non-BUSY error message, or
|
||||
* the data was all copied. */
|
||||
riscv_addr_t cur_addr = 0xbadbeef;
|
||||
riscv_addr_t cur_addr = riscv_read_debug_buffer_x(target, d_addr);
|
||||
riscv_addr_t fin_addr = address + (count * size);
|
||||
riscv_addr_t prev_addr = ((riscv_addr_t) address) - size;
|
||||
bool first = true;
|
||||
bool this_is_last_read = false;
|
||||
LOG_DEBUG("reading until final address 0x%" PRIx64, fin_addr);
|
||||
while (count > 1 && !this_is_last_read) {
|
||||
cur_addr = riscv_read_debug_buffer_x(target, d_addr);
|
||||
LOG_DEBUG("transferring burst starting at address 0x%" TARGET_PRIxADDR
|
||||
" (previous burst was 0x%" TARGET_PRIxADDR ")", cur_addr,
|
||||
prev_addr);
|
||||
assert(first || prev_addr < cur_addr);
|
||||
first = false;
|
||||
prev_addr = cur_addr;
|
||||
riscv_addr_t start = (cur_addr - address) / size;
|
||||
assert (cur_addr >= address);
|
||||
while (cur_addr < fin_addr) {
|
||||
// Invariant:
|
||||
// d_data contains *addr
|
||||
// d_addr contains addr + size
|
||||
|
||||
unsigned start = (cur_addr - address) / size;
|
||||
LOG_DEBUG("creating burst to read address 0x%" TARGET_PRIxADDR
|
||||
" up to 0x%" TARGET_PRIxADDR "; start=0x%d", cur_addr, fin_addr, start);
|
||||
assert(cur_addr >= address && cur_addr < fin_addr);
|
||||
struct riscv_batch *batch = riscv_batch_alloc(
|
||||
target,
|
||||
32,
|
||||
info->dmi_busy_delay + info->ac_busy_delay);
|
||||
|
||||
size_t reads = 0;
|
||||
size_t rereads = reads;
|
||||
for (riscv_addr_t i = start; i < count; ++i) {
|
||||
if (i == count - 1) {
|
||||
// don't do actual read in this batch,
|
||||
// we will do it later after we disable autoexec
|
||||
//
|
||||
// this is done to avoid reading more memory than requested
|
||||
// which in some special cases(like reading stack located
|
||||
// at the very top of RAM) may cause an exception
|
||||
this_is_last_read = true;
|
||||
} else {
|
||||
size_t const index =
|
||||
riscv_batch_add_dmi_read(
|
||||
for (riscv_addr_t addr = cur_addr; addr < fin_addr; addr += size) {
|
||||
size_t const index =
|
||||
riscv_batch_add_dmi_read(
|
||||
batch,
|
||||
riscv013_debug_buffer_register(target, r_data));
|
||||
assert(index == reads);
|
||||
}
|
||||
assert(index == reads);
|
||||
|
||||
reads++;
|
||||
if (riscv_batch_full(batch))
|
||||
|
@ -1397,26 +1436,21 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
|
||||
riscv_batch_run(batch);
|
||||
|
||||
// Note that if the scan resulted in a Busy DMI response, it
|
||||
// is this read to abstractcs that will cause the dmi_busy_delay
|
||||
// to be incremented if necessary. The loop condition above
|
||||
// catches the case where no writes went through at all.
|
||||
|
||||
bool retry_batch_transaction = false;
|
||||
// Wait for the target to finish performing the last abstract command,
|
||||
// and update our copy of cmderr.
|
||||
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
||||
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
|
||||
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
||||
info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
|
||||
|
||||
switch (info->cmderr) {
|
||||
case CMDERR_NONE:
|
||||
LOG_DEBUG("successful (partial?) memory write");
|
||||
LOG_DEBUG("successful (partial?) memory read");
|
||||
break;
|
||||
case CMDERR_BUSY:
|
||||
LOG_DEBUG("memory write resulted in busy response");
|
||||
riscv013_clear_abstract_error(target);
|
||||
LOG_DEBUG("memory read resulted in busy response");
|
||||
increase_ac_busy_delay(target);
|
||||
retry_batch_transaction = true;
|
||||
riscv_batch_free(batch);
|
||||
riscv013_clear_abstract_error(target);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
|
||||
|
@ -1427,51 +1461,45 @@ static int read_memory(struct target *target, target_addr_t address,
|
|||
riscv_batch_free(batch);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
if (retry_batch_transaction) continue;
|
||||
|
||||
for (size_t i = start; i < start + reads; ++i) {
|
||||
riscv_addr_t offset = size*i;
|
||||
riscv_addr_t t_addr = address + offset;
|
||||
uint8_t *t_buffer = buffer + offset;
|
||||
// Figure out how far we managed to read.
|
||||
riscv_addr_t next_addr = riscv_read_debug_buffer_x(target, d_addr);
|
||||
LOG_DEBUG("Batch read [0x%" TARGET_PRIxADDR ", 0x%" TARGET_PRIxADDR
|
||||
"); reads=%d", cur_addr, next_addr, (unsigned) reads);
|
||||
assert(next_addr >= address && next_addr <= fin_addr);
|
||||
assert(info->cmderr != CMDERR_NONE ||
|
||||
next_addr == cur_addr + reads * size);
|
||||
|
||||
if (this_is_last_read && i == start + reads - 1) {
|
||||
riscv013_set_autoexec(target, d_data, 0);
|
||||
// Now read whatever we got out of the batch.
|
||||
unsigned rereads = 0;
|
||||
for (riscv_addr_t addr = cur_addr - size; addr < next_addr - size;
|
||||
addr += size) {
|
||||
riscv_addr_t offset = addr - address;
|
||||
|
||||
// access debug buffer without executing a program - this address logic was taken from program.c
|
||||
int const off = (r_data - riscv_debug_buffer_addr(program.target)) / sizeof(program.debug_buffer[0]);
|
||||
value = riscv_read_debug_buffer(target, off);
|
||||
} else {
|
||||
uint64_t dmi_out = riscv_batch_get_dmi_read(batch, rereads);
|
||||
value = get_field(dmi_out, DTM_DMI_DATA);
|
||||
}
|
||||
uint64_t dmi_out = riscv_batch_get_dmi_read(batch, rereads);
|
||||
uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
|
||||
write_to_buf(buffer + offset, value, size);
|
||||
|
||||
rereads++;
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
t_buffer[0] = value;
|
||||
break;
|
||||
case 2:
|
||||
t_buffer[0] = value;
|
||||
t_buffer[1] = value >> 8;
|
||||
break;
|
||||
case 4:
|
||||
t_buffer[0] = value;
|
||||
t_buffer[1] = value >> 8;
|
||||
t_buffer[2] = value >> 16;
|
||||
t_buffer[3] = value >> 24;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("unsupported access size: %d", size);
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("M[0x%08lx] reads 0x%08x", (long)t_addr, value);
|
||||
LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", addr, value);
|
||||
}
|
||||
riscv_batch_free(batch);
|
||||
|
||||
cur_addr = next_addr;
|
||||
}
|
||||
|
||||
riscv013_set_autoexec(target, d_data, 0);
|
||||
|
||||
// Read the last word.
|
||||
|
||||
// Access debug buffer without executing a program. This
|
||||
// address logic was taken from program.c.
|
||||
uint32_t value = riscv013_read_debug_buffer(target, d_data);
|
||||
riscv_addr_t addr = cur_addr - size;
|
||||
write_to_buf(buffer + addr - address, value, size);
|
||||
LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", addr, value);
|
||||
|
||||
riscv_set_register(target, GDB_REGNO_S0, s0);
|
||||
riscv_set_register(target, GDB_REGNO_S1, s1);
|
||||
return ERROR_OK;
|
||||
|
@ -1692,7 +1720,7 @@ struct target_type riscv013_target =
|
|||
/*** 0.13-specific implementations of various RISC-V helper functions. ***/
|
||||
static riscv_reg_t riscv013_get_register(struct target *target, int hid, int rid)
|
||||
{
|
||||
LOG_DEBUG("reading register 0x%08x on hart %d", rid, hid);
|
||||
LOG_DEBUG("reading register %s on hart %d", gdb_regno_name(rid), hid);
|
||||
|
||||
riscv_set_current_hartid(target, hid);
|
||||
|
||||
|
@ -1706,7 +1734,7 @@ static riscv_reg_t riscv013_get_register(struct target *target, int hid, int rid
|
|||
LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, out);
|
||||
} else if (rid == GDB_REGNO_PRIV) {
|
||||
uint64_t dcsr;
|
||||
register_read_direct(target, &dcsr, CSR_DCSR);
|
||||
register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
||||
buf_set_u64((unsigned char *)&out, 0, 8, get_field(dcsr, CSR_DCSR_PRV));
|
||||
} else {
|
||||
int result = register_read_direct(target, &out, rid);
|
||||
|
@ -1724,7 +1752,8 @@ static riscv_reg_t riscv013_get_register(struct target *target, int hid, int rid
|
|||
|
||||
static void riscv013_set_register(struct target *target, int hid, int rid, uint64_t value)
|
||||
{
|
||||
LOG_DEBUG("writing register 0x%08x on hart %d", rid, hid);
|
||||
LOG_DEBUG("writing 0x%" PRIx64 " to register %s on hart %d", value,
|
||||
gdb_regno_name(rid), hid);
|
||||
|
||||
riscv_set_current_hartid(target, hid);
|
||||
|
||||
|
@ -1739,9 +1768,9 @@ static void riscv013_set_register(struct target *target, int hid, int rid, uint6
|
|||
assert(value == actual_value);
|
||||
} else if (rid == GDB_REGNO_PRIV) {
|
||||
uint64_t dcsr;
|
||||
register_read_direct(target, &dcsr, CSR_DCSR);
|
||||
register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
||||
dcsr = set_field(dcsr, CSR_DCSR_PRV, value);
|
||||
register_write_direct(target, CSR_DCSR, dcsr);
|
||||
register_write_direct(target, GDB_REGNO_DCSR, dcsr);
|
||||
} else {
|
||||
register_write_direct(target, rid, value);
|
||||
}
|
||||
|
@ -1900,36 +1929,6 @@ int riscv013_dmi_write_u64_bits(struct target *target)
|
|||
return info->abits + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH;
|
||||
}
|
||||
|
||||
void riscv013_reset_current_hart(struct target *target)
|
||||
{
|
||||
select_dmi(target);
|
||||
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
||||
control |= DMI_DMCONTROL_HALTREQ;
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
control = set_field(control, DMI_DMCONTROL_NDMRESET, 0);
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
|
||||
time_t start = time(NULL);
|
||||
|
||||
while (1) {
|
||||
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
||||
if (get_field(dmstatus, DMI_DMSTATUS_ALLHALTED)) {
|
||||
break;
|
||||
}
|
||||
if (time(NULL) - start > riscv_reset_timeout_sec) {
|
||||
LOG_ERROR("Hart didn't halt coming out of reset in %ds; "
|
||||
"dmstatus=0x%x"
|
||||
"Increase the timeout with riscv set_reset_timeout_sec.",
|
||||
riscv_reset_timeout_sec, dmstatus);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
control &= ~DMI_DMCONTROL_HALTREQ;
|
||||
dmi_write(target, DMI_DMCONTROL, control);
|
||||
}
|
||||
|
||||
/* Helper Functions. */
|
||||
static void riscv013_on_step_or_resume(struct target *target, bool step)
|
||||
|
@ -2063,8 +2062,23 @@ int riscv013_debug_buffer_register(struct target *target, riscv_addr_t addr)
|
|||
|
||||
void riscv013_clear_abstract_error(struct target *target)
|
||||
{
|
||||
uint32_t acs = dmi_read(target, DMI_ABSTRACTCS);
|
||||
dmi_write(target, DMI_ABSTRACTCS, acs);
|
||||
// Wait for busy to go away.
|
||||
time_t start = time(NULL);
|
||||
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
||||
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY)) {
|
||||
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
||||
|
||||
if (time(NULL) - start > riscv_command_timeout_sec) {
|
||||
LOG_ERROR("abstractcs.busy is not going low after %d seconds "
|
||||
"(abstractcs=0x%x). The target is either really slow or "
|
||||
"broken. You could increase the timeout with riscv "
|
||||
"set_reset_timeout_sec.",
|
||||
riscv_command_timeout_sec, abstractcs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Clear the error status.
|
||||
dmi_write(target, DMI_ABSTRACTCS, abstractcs & DMI_ABSTRACTCS_CMDERR);
|
||||
}
|
||||
|
||||
#define COMPLIANCE_TEST(b, message) { \
|
||||
|
|
|
@ -394,31 +394,43 @@ static int add_trigger(struct target *target, struct trigger *trigger)
|
|||
{
|
||||
RISCV_INFO(r);
|
||||
|
||||
// In RTOS mode, we need to set the same trigger in the same slot on every
|
||||
// hart, to keep up the illusion that each hart is a thread running on the
|
||||
// same core.
|
||||
|
||||
// Otherwise, we just set the trigger on the one hart this target deals
|
||||
// with.
|
||||
|
||||
riscv_reg_t tselect[RISCV_MAX_HARTS];
|
||||
|
||||
int first_hart = -1;
|
||||
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||
if (!riscv_hart_enabled(target, hartid))
|
||||
continue;
|
||||
if (first_hart < 0)
|
||||
first_hart = hartid;
|
||||
tselect[hartid] = riscv_get_register_on_hart(target, hartid,
|
||||
GDB_REGNO_TSELECT);
|
||||
}
|
||||
assert(first_hart >= 0);
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < r->trigger_count[0]; i++) {
|
||||
for (i = 0; i < r->trigger_count[first_hart]; i++) {
|
||||
if (r->trigger_unique_id[i] != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
riscv_set_register_on_hart(target, 0, GDB_REGNO_TSELECT, i);
|
||||
riscv_set_register_on_hart(target, first_hart, GDB_REGNO_TSELECT, i);
|
||||
|
||||
uint64_t tdata1 = riscv_get_register_on_hart(target, 0, GDB_REGNO_TDATA1);
|
||||
uint64_t tdata1 = riscv_get_register_on_hart(target, first_hart, GDB_REGNO_TDATA1);
|
||||
int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
|
||||
|
||||
int result = ERROR_OK;
|
||||
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||
for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
|
||||
LOG_DEBUG(">>> hartid=%d", hartid);
|
||||
if (!riscv_hart_enabled(target, hartid))
|
||||
continue;
|
||||
if (hartid > 0) {
|
||||
if (hartid > first_hart) {
|
||||
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
|
||||
}
|
||||
switch (type) {
|
||||
|
@ -448,14 +460,14 @@ static int add_trigger(struct target *target, struct trigger *trigger)
|
|||
break;
|
||||
}
|
||||
|
||||
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||
for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
|
||||
if (!riscv_hart_enabled(target, hartid))
|
||||
continue;
|
||||
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT,
|
||||
tselect[hartid]);
|
||||
}
|
||||
|
||||
if (i >= r->trigger_count[0]) {
|
||||
if (i >= r->trigger_count[first_hart]) {
|
||||
LOG_ERROR("Couldn't find an available hardware trigger.");
|
||||
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
|
||||
}
|
||||
|
@ -507,19 +519,30 @@ static int remove_trigger(struct target *target, struct trigger *trigger)
|
|||
{
|
||||
RISCV_INFO(r);
|
||||
|
||||
int first_hart = -1;
|
||||
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||
if (!riscv_hart_enabled(target, hartid))
|
||||
continue;
|
||||
if (first_hart < 0) {
|
||||
first_hart = hartid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(first_hart >= 0);
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < r->trigger_count[0]; i++) {
|
||||
for (i = 0; i < r->trigger_count[first_hart]; i++) {
|
||||
if (r->trigger_unique_id[i] == trigger->unique_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= r->trigger_count[0]) {
|
||||
if (i >= r->trigger_count[first_hart]) {
|
||||
LOG_ERROR("Couldn't find the hardware resources used by hardware "
|
||||
"trigger.");
|
||||
return ERROR_FAIL;
|
||||
}
|
||||
LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
|
||||
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||
for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
|
||||
if (!riscv_hart_enabled(target, hartid))
|
||||
continue;
|
||||
riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);
|
||||
|
@ -629,7 +652,7 @@ static int riscv_examine(struct target *target)
|
|||
{
|
||||
LOG_DEBUG("riscv_examine()");
|
||||
if (target_was_examined(target)) {
|
||||
LOG_DEBUG("Target was already examined.\n");
|
||||
LOG_DEBUG("Target was already examined.");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
|
@ -676,14 +699,13 @@ static int old_or_new_riscv_halt(struct target *target)
|
|||
return riscv_openocd_halt(target);
|
||||
}
|
||||
|
||||
static int oldriscv_assert_reset(struct target *target)
|
||||
static int riscv_assert_reset(struct target *target)
|
||||
{
|
||||
LOG_DEBUG("RISCV ASSERT RESET");
|
||||
struct target_type *tt = get_target_type(target);
|
||||
return tt->assert_reset(target);
|
||||
}
|
||||
|
||||
static int oldriscv_deassert_reset(struct target *target)
|
||||
static int riscv_deassert_reset(struct target *target)
|
||||
{
|
||||
LOG_DEBUG("RISCV DEASSERT RESET");
|
||||
struct target_type *tt = get_target_type(target);
|
||||
|
@ -691,24 +713,6 @@ static int oldriscv_deassert_reset(struct target *target)
|
|||
}
|
||||
|
||||
|
||||
static int old_or_new_riscv_assert_reset(struct target *target)
|
||||
{
|
||||
RISCV_INFO(r);
|
||||
if (r->is_halted == NULL)
|
||||
return oldriscv_assert_reset(target);
|
||||
else
|
||||
return riscv_openocd_assert_reset(target);
|
||||
}
|
||||
|
||||
static int old_or_new_riscv_deassert_reset(struct target *target)
|
||||
{
|
||||
RISCV_INFO(r);
|
||||
if (r->is_halted == NULL)
|
||||
return oldriscv_deassert_reset(target);
|
||||
else
|
||||
return riscv_openocd_deassert_reset(target);
|
||||
}
|
||||
|
||||
static int oldriscv_resume(struct target *target, int current, uint32_t address,
|
||||
int handle_breakpoints, int debug_execution)
|
||||
{
|
||||
|
@ -1115,28 +1119,6 @@ int riscv_openocd_step(
|
|||
return out;
|
||||
}
|
||||
|
||||
int riscv_openocd_assert_reset(struct target *target)
|
||||
{
|
||||
LOG_DEBUG("asserting reset for all harts");
|
||||
int out = riscv_reset_all_harts(target);
|
||||
if (out != ERROR_OK) {
|
||||
LOG_ERROR("unable to reset all harts");
|
||||
return out;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
int riscv_openocd_deassert_reset(struct target *target)
|
||||
{
|
||||
LOG_DEBUG("deasserting reset for all harts");
|
||||
if (target->reset_halt)
|
||||
riscv_halt_all_harts(target);
|
||||
else
|
||||
riscv_resume_all_harts(target);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
/* Command Handlers */
|
||||
COMMAND_HANDLER(riscv_set_command_timeout_sec) {
|
||||
|
||||
|
@ -1243,8 +1225,8 @@ struct target_type riscv_target =
|
|||
.resume = old_or_new_riscv_resume,
|
||||
.step = old_or_new_riscv_step,
|
||||
|
||||
.assert_reset = old_or_new_riscv_assert_reset,
|
||||
.deassert_reset = old_or_new_riscv_deassert_reset,
|
||||
.assert_reset = riscv_assert_reset,
|
||||
.deassert_reset = riscv_deassert_reset,
|
||||
|
||||
.read_memory = riscv_read_memory,
|
||||
.write_memory = riscv_write_memory,
|
||||
|
@ -1341,33 +1323,6 @@ int riscv_resume_one_hart(struct target *target, int hartid)
|
|||
return ERROR_OK;
|
||||
}
|
||||
|
||||
int riscv_reset_all_harts(struct target *target)
|
||||
{
|
||||
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
||||
if (!riscv_hart_enabled(target, i))
|
||||
continue;
|
||||
|
||||
riscv_reset_one_hart(target, i);
|
||||
}
|
||||
|
||||
riscv_invalidate_register_cache(target);
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
int riscv_reset_one_hart(struct target *target, int hartid)
|
||||
{
|
||||
RISCV_INFO(r);
|
||||
LOG_DEBUG("resetting hart %d", hartid);
|
||||
riscv_halt_one_hart(target, hartid);
|
||||
riscv_set_current_hartid(target, hartid);
|
||||
r->reset_current_hart(target);
|
||||
/* At this point the hart must be halted. On platforms that support
|
||||
* "reset halt" exactly we expect the hart to have been halted before
|
||||
* executing any instructions, while on older cores it'll just have
|
||||
* halted quickly. */
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
int riscv_step_rtos_hart(struct target *target)
|
||||
{
|
||||
RISCV_INFO(r);
|
||||
|
@ -1741,6 +1696,8 @@ const char *gdb_regno_name(enum gdb_regno regno)
|
|||
sprintf(buf, "x%d", regno - GDB_REGNO_XPR0);
|
||||
} else if (regno >= GDB_REGNO_CSR0 && regno <= GDB_REGNO_CSR4095) {
|
||||
sprintf(buf, "csr%d", regno - GDB_REGNO_CSR0);
|
||||
} else if (regno >= GDB_REGNO_FPR0 && regno <= GDB_REGNO_FPR31) {
|
||||
sprintf(buf, "f%d", regno - GDB_REGNO_FPR0);
|
||||
} else {
|
||||
sprintf(buf, "gdb_regno_%d", regno);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,9 @@ typedef struct {
|
|||
unsigned trigger_count[RISCV_MAX_HARTS];
|
||||
|
||||
/* For each physical trigger, contains -1 if the hwbp is available, or the
|
||||
* unique_id of the breakpoint/watchpoint that is using it. */
|
||||
* unique_id of the breakpoint/watchpoint that is using it.
|
||||
* Note that in RTOS mode the triggers are the same across all harts the
|
||||
* target controls, while otherwise only a single hart is controlled. */
|
||||
int trigger_unique_id[RISCV_MAX_HWBPS];
|
||||
|
||||
/* The address of the debug RAM buffer. */
|
||||
|
@ -103,8 +105,8 @@ typedef struct {
|
|||
void (*fill_dmi_write_u64)(struct target *target, char *buf, int a, uint64_t d);
|
||||
void (*fill_dmi_read_u64)(struct target *target, char *buf, int a);
|
||||
void (*fill_dmi_nop_u64)(struct target *target, char *buf);
|
||||
void (*reset_current_hart)(struct target *target);
|
||||
int (*test_compliance)(struct target *target);
|
||||
|
||||
int (*test_compliance)(struct target *target);
|
||||
} riscv_info_t;
|
||||
|
||||
/* Wall-clock timeout for a command/access. Settable via RISC-V Target commands.*/
|
||||
|
@ -162,8 +164,6 @@ int riscv_halt_all_harts(struct target *target);
|
|||
int riscv_halt_one_hart(struct target *target, int hartid);
|
||||
int riscv_resume_all_harts(struct target *target);
|
||||
int riscv_resume_one_hart(struct target *target, int hartid);
|
||||
int riscv_reset_all_harts(struct target *target);
|
||||
int riscv_reset_one_hart(struct target *target, int hartid);
|
||||
|
||||
/* Steps the hart that's currently selected in the RTOS, or if there is no RTOS
|
||||
* then the only hart. */
|
||||
|
|
|
@ -3037,16 +3037,16 @@ static void handle_md_output(struct command_context *cmd_ctx,
|
|||
const char *value_fmt;
|
||||
switch (size) {
|
||||
case 8:
|
||||
value_fmt = "%16.16llx ";
|
||||
value_fmt = "%16.16"PRIx64" ";
|
||||
break;
|
||||
case 4:
|
||||
value_fmt = "%8.8x ";
|
||||
value_fmt = "%8.8"PRIx64" ";
|
||||
break;
|
||||
case 2:
|
||||
value_fmt = "%4.4x ";
|
||||
value_fmt = "%4.4"PRIx64" ";
|
||||
break;
|
||||
case 1:
|
||||
value_fmt = "%2.2x ";
|
||||
value_fmt = "%2.2"PRIx64" ";
|
||||
break;
|
||||
default:
|
||||
/* "can't happen", caller checked */
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#
|
||||
# Freescale Kinetis KE02 devices
|
||||
#
|
||||
|
||||
set CHIPNAME ke02
|
||||
source [find target/kex.cfg]
|
|
@ -1,6 +0,0 @@
|
|||
#
|
||||
# Freescale Kinetis KE04 devices
|
||||
#
|
||||
|
||||
set CHIPNAME ke04
|
||||
source [find target/kex.cfg]
|
|
@ -1,6 +0,0 @@
|
|||
#
|
||||
# Freescale Kinetis KE06 devices
|
||||
#
|
||||
|
||||
set CHIPNAME ke06
|
||||
source [find target/kex.cfg]
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Freescale Kinetis KE series devices
|
||||
# Freescale Kinetis KE0x series devices
|
||||
#
|
||||
|
||||
source [find target/swj-dp.tcl]
|
||||
|
@ -21,11 +21,7 @@ if { [info exists WORKAREASIZE] } {
|
|||
if { [info exists CPUTAPID] } {
|
||||
set _CPUTAPID $CPUTAPID
|
||||
} else {
|
||||
if { [using_jtag] } {
|
||||
set _CPUTAPID 0x4ba00477
|
||||
} {
|
||||
set _CPUTAPID 0x2ba01477
|
||||
}
|
||||
set _CPUTAPID 0x0bc11477
|
||||
}
|
||||
|
||||
swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# NXP (Freescale) Kinetis KE1xF devices
|
||||
#
|
||||
|
||||
set CHIPNAME ke
|
||||
|
||||
source [find target/kx.cfg]
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# NXP (Freescale) Kinetis KE1xZ devices
|
||||
#
|
||||
|
||||
set CHIPNAME ke
|
||||
|
||||
source [find target/klx.cfg]
|
|
@ -1,5 +1,6 @@
|
|||
#
|
||||
# Freescale Kinetis KL series devices
|
||||
# NXP (former Freescale) Kinetis KL series devices
|
||||
# Also used for Cortex-M0+ equipped members of KVx and KE1xZ series
|
||||
#
|
||||
|
||||
source [find target/swj-dp.tcl]
|
||||
|
@ -31,11 +32,13 @@ target create $_TARGETNAME cortex_m -chain-position $_CHIPNAME.cpu
|
|||
|
||||
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
|
||||
|
||||
set _FLASHNAME $_CHIPNAME.flash
|
||||
set _FLASHNAME $_CHIPNAME.pflash
|
||||
flash bank $_FLASHNAME kinetis 0 0 0 0 $_TARGETNAME
|
||||
kinetis create_banks
|
||||
|
||||
# Table 5-1. Clock Summary of KL25 Sub-Family Reference Manual
|
||||
# specifies up to 1MHz for VLPR mode.
|
||||
# specifies up to 1MHz for VLPR mode and up to 24MHz for run mode;
|
||||
# Table 17 of Sub-Family Data Sheet rev4 lists 25MHz as the maximum frequency.
|
||||
adapter_khz 1000
|
||||
|
||||
reset_config srst_nogate
|
||||
|
@ -51,10 +54,9 @@ if {![using_hla]} {
|
|||
cortex_m reset_config sysresetreq
|
||||
}
|
||||
|
||||
# Table 5-1. Clock Summary of KL25 Sub-Family Reference Manual
|
||||
# specifies up to 24MHz for run mode; Table 17 of Sub-Family Data
|
||||
# Sheet rev4 lists 25MHz as the maximum frequency.
|
||||
# Uncoment only if VLPR mode is not used
|
||||
#$_TARGETNAME configure -event reset-init {
|
||||
# adapter_khz 24000
|
||||
#}
|
||||
# Disable watchdog not to disturb OpenOCD algorithms running on MCU
|
||||
# (e.g. armv7m_checksum_memory() in verify_image)
|
||||
# Flash driver also disables watchdog before FTFA flash programming.
|
||||
$_TARGETNAME configure -event reset-init {
|
||||
kinetis disable_wdog
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#
|
||||
# Freescale Kinetis Kx series devices
|
||||
# NXP (former Freescale) Kinetis Kx series devices
|
||||
# Also used for Cortex-M4 equipped members of KVx and KE1xF series
|
||||
#
|
||||
|
||||
source [find target/swj-dp.tcl]
|
||||
|
@ -35,8 +36,9 @@ target create $_TARGETNAME cortex_m -chain-position $_CHIPNAME.cpu
|
|||
|
||||
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
|
||||
|
||||
set _FLASHNAME $_CHIPNAME.flash
|
||||
set _FLASHNAME $_CHIPNAME.pflash
|
||||
flash bank $_FLASHNAME kinetis 0 0 0 0 $_TARGETNAME
|
||||
kinetis create_banks
|
||||
|
||||
adapter_khz 1000
|
||||
|
||||
|
@ -52,3 +54,11 @@ if {![using_hla]} {
|
|||
# perform a soft reset
|
||||
cortex_m reset_config sysresetreq
|
||||
}
|
||||
|
||||
# Disable watchdog not to disturb OpenOCD algorithms running on MCU
|
||||
# (e.g. armv7m_checksum_memory() in verify_image)
|
||||
# Flash driver also disables watchdog before FTFA flash programming.
|
||||
$_TARGETNAME configure -event reset-init {
|
||||
kinetis disable_wdog
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ set _TARGETNAME $_CHIPNAME.cpu
|
|||
jtag newtap zynq_pl bs -irlen 6 -ircapture 0x1 -irmask 0x03 \
|
||||
-expected-id 0x23727093 \
|
||||
-expected-id 0x13722093 \
|
||||
-expected-id 0x03727093
|
||||
-expected-id 0x03727093 \
|
||||
-expected-id 0x03736093
|
||||
|
||||
jtag newtap $_CHIPNAME dap -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id 0x4ba00477
|
||||
|
||||
|
|
Loading…
Reference in New Issue