Improve support for GD32VF103 MCU (#538)

* riscv: work around buggy hart states during reset in some DMs

As described in the comment this change adds, the GD32VF103 DM reports
that the hart is in more than one state while it is resetting. Because
of this, the current code acknowledges resets before they actually
complete. This sometimes prevents havereset from getting cleared as
intended, leading to a spurious "Hart 0 unexpectedly reset!" message the
next time riscv_is_halted() gets called.

To work around this, check for the absence of the unavailable state
rather than the presence of the running or halted states. This behavior
is also arguably more true to the spec than what exists now: Section 3.2
states that "The system may take an arbitrarily long time to come out of
reset, as reported by allunavail, anyunavail."

Change-Id: I34e90a16233125608bce8e4c2414dbead637600e
Signed-off-by: Thomas Hebb <tommyhebb@gmail.com>

* riscv: support custom reset-assert scripts

The reset-assert event is used, if present, to override the default
reset logic for ARM and MIPS cores. Do the same for RISC-V so that
devices with buggy ndmreset functionality (like GD32VF103) or
nonstandard reset sequences can specify the appropriate logic in Tcl.

Change-Id: I5e12077d67509853edb8ef3ad3f037f293a5fbb6
Signed-off-by: Thomas Hebb <tommyhebb@gmail.com>

* tcl/target: support GD32VF103 RISC-V MCU

The GD32VF103 is a low-cost 32-bit RISC-V microcontroller with
peripherals that are more-or-less compatible with the STM32F103 ARM
microcontroller. It is available on several low-cost dev boards, such as
the Sipeed Longan Nano, which is what I am testing on.

Add initial support for this chip, including a workaround for a buggy
ndmreset line (i.e. one that doesn't actually trigger a reset) in its
integrated debug module. Use the existing GD32VF103 flash driver that
was ported from the vendor's code in commit 48e40f3513 ("Add support
for GD32VF103 flash").

Change-Id: Iadac47ceb5437b8e18f3d35901388f10fef9f876
Signed-off-by: Thomas Hebb <tommyhebb@gmail.com>

* tcl/target/gd32vf103: add main flash alias

The GD32VF103 creates an alias to either main flash or the bootloader at
0x0, depending on how it was booted. As such, we want to indicate to
debuggers that the memory at 0x0 is flash and so cannot support software
breakpoints. To do this, add an alias to the main flash in the config.
This isn't strictly accurate in the case where we're running the
bootloader, but it still suits our purpose of fixing breakpoint
behavior.

Change-Id: I9eb8462d354f096eee231c0e5e2bffa538a5903e
Signed-off-by: Thomas Hebb <tommyhebb@gmail.com>
This commit is contained in:
Tom Hebb 2020-10-01 11:06:11 -07:00 committed by GitHub
parent 6db3ed2c86
commit e2cfd4e063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 14 deletions

View File

@ -2094,7 +2094,10 @@ static int assert_reset(struct target *target)
uint32_t control_base = set_field(0, DM_DMCONTROL_DMACTIVE, 1);
if (target->rtos) {
if (target_has_event_action(target, TARGET_EVENT_RESET_ASSERT)) {
/* Run the user-supplied script if there is one. */
target_handle_event(target, TARGET_EVENT_RESET_ASSERT);
} else if (target->rtos) {
/* There's only one target, and OpenOCD thinks each hart is a thread.
* We must reset them all. */
@ -2166,16 +2169,7 @@ static int deassert_reset(struct target *target)
index = r->current_hartid;
}
char *operation;
uint32_t expected_field;
if (target->reset_halt) {
operation = "halt";
expected_field = DM_DMSTATUS_ALLHALTED;
} else {
operation = "run";
expected_field = DM_DMSTATUS_ALLRUNNING;
}
LOG_DEBUG("Waiting for hart %d to %s out of reset.", index, operation);
LOG_DEBUG("Waiting for hart %d to come out of reset.", index);
while (1) {
int result = dmstatus_read_timeout(target, &dmstatus, true,
riscv_reset_timeout_sec);
@ -2186,13 +2180,20 @@ static int deassert_reset(struct target *target)
index, riscv_reset_timeout_sec);
if (result != ERROR_OK)
return result;
if (get_field(dmstatus, expected_field))
/* Certain debug modules, like the one in GD32VF103
* MCUs, violate the specification's requirement that
* each hart is in "exactly one of four states" and,
* during reset, report harts as both unavailable and
* halted/running. To work around this, we check for
* the absence of the unavailable state rather than
* the presence of any other state. */
if (!get_field(dmstatus, DM_DMSTATUS_ALLUNAVAIL))
break;
if (time(NULL) - start > riscv_reset_timeout_sec) {
LOG_ERROR("Hart %d didn't %s coming out of reset in %ds; "
LOG_ERROR("Hart %d didn't leave reset in %ds; "
"dmstatus=0x%x; "
"Increase the timeout with riscv set_reset_timeout_sec.",
index, operation, riscv_reset_timeout_sec, dmstatus);
index, riscv_reset_timeout_sec, dmstatus);
return ERROR_FAIL;
}
}

96
tcl/target/gd32vf103.cfg Normal file
View File

@ -0,0 +1,96 @@
reset_config srst_nogate
set _CHIPNAME gd32vf103
# The vendor's configuration expects an ID of 0x1e200a6d, but this one is what
# I have on my board (Sipeed Longan Nano, GD32VF103CBT6).
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x1000563d
jtag newtap $_CHIPNAME bs -irlen 5 -expected-id 0x790007a3
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
$_TARGETNAME riscv set_enable_virt2phys off
proc default_mem_access {} {
riscv set_mem_access progbuf
}
default_mem_access
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size 0x1000 -work-area-backup 1
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME gd32vf103 0x08000000 0 0 0 $_TARGETNAME
# Address 0 is only aliased to main flash when the chip is not running its
# built-in bootloader. When it is, it's instead aliased to a read only section
# of flash at 0x1fffb000. However, we can't detect or dynamically switch this,
# so just pretend it's always aliased to main flash. We need to tell OpenOCD
# about this alias because otherwise we'll try to use software breakpoints on
# code in flash, which don't work because flash mappings are read-only.
flash bank $_CHIPNAME.flashalias virtual 0x0 0 0 0 $_TARGETNAME $_FLASHNAME
# On this chip, ndmreset (the debug module bit that triggers a software reset)
# doesn't work. So for JTAG connections without an SRST, we need to trigger a
# reset manually. This is an undocumented reset sequence that's used by the
# JTAG flashing script in the vendor-supplied GD32VF103 PlatformIO plugin:
#
# https://github.com/sipeed/platform-gd32v/commit/f9cbb44819bc05dd2010cc815c32be0486800cc2
#
$_TARGETNAME configure -event reset-assert {
set dmcontrol 0x10
set dmcontrol_dmactive [expr 1 << 0]
set dmcontrol_haltreq [expr 1 << 31]
global _RESETMODE
global _TARGETNAME
# Halt the core so that we can write to memory. We do this first so
# that it doesn't clobber our dmcontrol configuration.
halt
# Set haltreq appropriately for the type of reset we're doing. This
# replicates what the generic RISC-V reset_assert() function would
# do if we weren't overriding it. The $_RESETMODE hack sucks, but
# it's the least invasive way to determine whether we need to halt,
# and psoc6.cfg already uses the same trick. (reset_deassert(), which
# does run, also does this, but at that point it may be too late: the
# reset has already been triggered, so there's a race between it and
# the haltreq write.)
#
# If we didn't override the generic handler, we'd actually still have
# to do this: the default handler sets ndmreset, which prevents memory
# access even though it doesn't actually trigger a reset on this chip.
# So we'd need to unset it here, which involves a write to dmcontrol,
# Since haltreq is write-only and there's no way to leave it unchanged,
# we'd have to figure out its proper value anyway.
set val $dmcontrol_dmactive
if {$_RESETMODE ne "run"} {
set val [expr $val | $dmcontrol_haltreq]
}
$_TARGETNAME riscv dmi_write $dmcontrol $val
# Unlock 0xe0042008 so that the next write triggers a reset
$_TARGETNAME mww 0xe004200c 0x4b5a6978
# We need to trigger the reset using abstract memory access, since
# progbuf access tries to read a status code out of a core register
# after the write happens, which fails when the core is in reset.
riscv set_mem_access abstract
# Go!
$_TARGETNAME mww 0xe0042008 0x1
# Put the memory access mode back to what it was.
default_mem_access
}
# Capture the mode of a given reset so that we can use it later in the
# reset-assert handler.
proc init_reset { mode } {
global _RESETMODE
set _RESETMODE $mode
if {[using_jtag]} {
jtag arp_init-reset
}
}