diff --git a/docs/options.md b/docs/options.md index b2fe62a62..58407f5ff 100644 --- a/docs/options.md +++ b/docs/options.md @@ -234,6 +234,22 @@ Whether to preserve existing flash content for ranges of sectors that will be er with new data. +load.pre_reset +str +No default + +Specify the type of reset to perform before programming. The value must be one of +'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'. + + +load.post_reset +str +No default + +Specify the type of reset to perform after programming. The value must be one of +'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'. + + logging str, dict No default diff --git a/pyocd/core/options.py b/pyocd/core/options.py index 4a10c73a2..fbb8a275f 100644 --- a/pyocd/core/options.py +++ b/pyocd/core/options.py @@ -44,7 +44,7 @@ class OptionInfo(NamedTuple): "target memory."), OptionInfo('chip_erase', str, "sector", "Whether to perform a chip erase or sector erases when programming flash. The value must be" - " one of \"auto\", \"sector\", or \"chip\"."), + " one of 'auto', 'sector', or 'chip'."), OptionInfo('cmsis_dap.prefer_v1', bool, False, "If a device provides both CMSIS-DAP v1 and v2 interfaces, use the v1 interface in preference of v2. " "Normal behaviour is to prefer the v2 interface. This option is primarily intended for testing."), @@ -89,6 +89,12 @@ class OptionInfo(NamedTuple): OptionInfo('keep_unwritten', bool, False, "Whether to preserve existing flash content for ranges of sectors that will be erased but not " "written with new data. Default is False."), + OptionInfo('load.pre_reset', str, None, + "Specify the type of reset to perform before programming. The value must be one of" + " 'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'."), + OptionInfo('load.post_reset', str, None, + "Specify the type of reset to perform after programming. The value must be one of" + " 'off', 'default', 'hardware', 'system', 'core', 'n_srst', 'sysresetreq', 'vectreset' or 'emulated'."), OptionInfo('logging', (str, dict), None, "Logging configuration dictionary, or path to YAML file containing logging configuration."), OptionInfo('no_config', bool, False, @@ -171,7 +177,7 @@ class OptionInfo(NamedTuple): OptionInfo('rtos.name', str, None, "Name of the RTOS plugin to use. If not set, all RTOS plugins are given a chance to load."), OptionInfo('semihost_console_type', str, 'telnet', - "If set to \"telnet\" then the semihosting telnet server will be started, otherwise " + "If set to 'telnet' then the semihosting telnet server will be started, otherwise " "semihosting will print to the console."), OptionInfo('semihost_use_syscalls', bool, False, "Whether to use GDB syscalls for semihosting file access operations."), diff --git a/pyocd/coresight/cortex_m.py b/pyocd/coresight/cortex_m.py index 7cd23ce94..b3c51ac0a 100644 --- a/pyocd/coresight/cortex_m.py +++ b/pyocd/coresight/cortex_m.py @@ -873,24 +873,22 @@ def _get_actual_reset_type(self, reset_type: Optional[Target.ResetType]) -> Targ # Select the actual reset type to use, based on priority: # 1. reset_type parameter - # 2. cbuild-run user option - # 3. session option + # 2. session option + # 3. cbuild-run user option (modifies default_reset_type) # 4. core default_reset_type property if reset_type is None: # No explicit reset_type parameter provided, check user options. - if self.session.options.is_set('cbuild_run'): + option_reset_type = self.session.options.get('reset_type') + if option_reset_type == 'default': _reset_type = self.default_reset_type - elif self.session.options.get('reset_type') != 'default': - option_reset_type = self.session.options.get('reset_type') + else: try: _reset_type = cmdline.convert_reset_type(option_reset_type) except ValueError: LOG.warning("invalid reset type '%s' specified in user options; falling back to 'default'", option_reset_type) _reset_type = self.default_reset_type - else: - _reset_type = self.default_reset_type reset_type = _reset_type @@ -980,7 +978,7 @@ def _post_reset_core_accessibility_test(self) -> None: else: LOG.debug("Core %d did not come out of reset within timeout", self.core_number) - def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]: + def reset_hook(self, reset_type: Target.ResetType) -> None: # Map our reset type to a reset sequence name. result = self.call_delegate('will_reset', core=self, reset_type=reset_type) @@ -988,14 +986,20 @@ def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]: # Check if the reset type is DEFAULT, SYSTEM, CORE, or HARDWARE. # These map to standard debug sequence names. Other reset types # are handled directly by _perform_reset(). - if reset_type in (Target.ResetType.DEFAULT, - Target.ResetType.HARDWARE, - Target.ResetType.SYSTEM, - Target.ResetType.CORE): + STANDARD_RESET_SEQUENCES = { + 'ResetHardware': Target.ResetType.HARDWARE, + 'ResetSystem': Target.ResetType.SYSTEM, + 'ResetProcessor': Target.ResetType.CORE, + } + if reset_type in (Target.ResetType.DEFAULT, *STANDARD_RESET_SEQUENCES.values()): # Check if a custom reset sequence is defined. if reset_type == Target.ResetType.DEFAULT: # Use the core's default reset sequence. reset_sequence_name = self.debug_sequence_delegate.default_reset_sequence(self.node_name) + # See if the default reset sequence name maps to a standard reset type. + if reset_sequence_name in STANDARD_RESET_SEQUENCES: + # Update reset_type so we can use it later in _perform_reset(). + reset_type = STANDARD_RESET_SEQUENCES[reset_sequence_name] elif reset_type == Target.ResetType.HARDWARE: reset_sequence_name = 'ResetHardware' elif reset_type == Target.ResetType.SYSTEM: @@ -1008,13 +1012,18 @@ def reset_hook(self, reset_type: Target.ResetType) -> Optional[bool]: LOG.debug("running '%s' debug sequence, core %d", reset_sequence_name, self.core_number) self.debug_sequence_delegate.run_sequence(reset_sequence_name, pname=self.node_name) result = True - elif reset_sequence_name not in ('ResetSystem', 'ResetProcessor', 'ResetHardware'): + elif reset_sequence_name not in STANDARD_RESET_SEQUENCES: # Custom reset sequence was specified but not found. Warn the user, but don't fall back # to a different reset method. LOG.error("skipping missing '%s' debug sequence, core %d", reset_sequence_name, self.core_number) result = True - return result + if not result: + # No delegate or debug sequence handled the reset, so do it ourselves. + LOG.debug("no delegate or debug sequence handled reset; performing %s reset, core %d", + reset_type.name, self.core_number) + + self._perform_reset(reset_type) def _inner_reset(self, reset_type: Optional[Target.ResetType], is_halting: bool) -> None: """@brief Internal routine for resetting the core. @@ -1029,10 +1038,7 @@ def _inner_reset(self, reset_type: Optional[Target.ResetType], is_halting: bool) self._run_token += 1 - # Give the delegate a chance to overide reset. If the delegate returns True, then it - # handled the reset on its own. - if not self.reset_hook(reset_type): - self._perform_reset(reset_type) + self.reset_hook(reset_type) # Unless this is a halting reset, make sure the core is not halted. Some DFP debug sequences # (or user scripts) can leave the core halted after a reset. diff --git a/pyocd/subcommands/load_cmd.py b/pyocd/subcommands/load_cmd.py index edf15b689..567a10062 100644 --- a/pyocd/subcommands/load_cmd.py +++ b/pyocd/subcommands/load_cmd.py @@ -25,6 +25,7 @@ from ..core.target import Target from ..flash.file_programmer import FileProgrammer from ..utility.cmdline import ( + convert_reset_type, convert_session_options, int_base_0, ) @@ -111,16 +112,24 @@ def invoke(self) -> int: # Get a list of all secondary cores. secondary_cores = [c for c in session.target.cores.values() if c != session.target.primary_core] - try: - # Set reset catch for all secondary cores. - for core in secondary_cores: - core.set_reset_catch() - # Reset and halt the primary core. - session.target.reset_and_halt() - finally: - # Clear reset catch for all secondary cores. - for core in secondary_cores: - core.clear_reset_catch() + pre_reset = session.options.get('load.pre_reset') + if pre_reset != "off": + try: + reset_type = convert_reset_type(pre_reset) if pre_reset else None + except ValueError: + LOG.error("Invalid pre-reset option: %s", pre_reset) + return 1 + + try: + # Set reset catch for all secondary cores. + for core in secondary_cores: + core.set_reset_catch(reset_type) + # Reset and halt the primary core. + session.target.reset_and_halt(reset_type) + finally: + # Clear reset catch for all secondary cores. + for core in secondary_cores: + core.clear_reset_catch(reset_type) if not self._args.file and self._args.cbuild_run: # Populate file list from cbuild-run output if not provided explicitly @@ -164,7 +173,14 @@ def invoke(self) -> int: file_format=file_format) # Reset the target after programming unless --no-reset was specified. - if not self._args.no_reset: - session.target.reset(Target.ResetType.NSRST) + post_reset = session.options.get('load.post_reset') + if not self._args.no_reset and post_reset != 'off': + try: + reset_type = convert_reset_type(post_reset) if post_reset else None + except ValueError: + LOG.error("Invalid post-reset option: %s", post_reset) + return 1 + + session.target.reset(reset_type) return 0