diff --git a/boards/common/stlink_gdbserver.board.cmake b/boards/common/stlink_gdbserver.board.cmake new file mode 100644 index 00000000000..5b88f0cb745 --- /dev/null +++ b/boards/common/stlink_gdbserver.board.cmake @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 + +board_set_debugger_ifnset(stlink_gdbserver) +board_finalize_runner_args(stlink_gdbserver) diff --git a/doc/develop/flash_debug/host-tools.rst b/doc/develop/flash_debug/host-tools.rst index 3ccc61f1aa5..aa691ebe29c 100644 --- a/doc/develop/flash_debug/host-tools.rst +++ b/doc/develop/flash_debug/host-tools.rst @@ -525,6 +525,50 @@ to ``rfp-cli`` when flashing: west flash --rfp-cli ~/Downloads/RFP_CLI_Linux_V31800_x64/linux-x64/rfp-cli +.. _stm32cubeclt-host-tools: +.. _runner_stlink_gdbserver: + +STM32CubeCLT Flash & Debug Host Tools +************************************* + +STMicroelectronics provides `STM32CubeCLT`_ as an official all-in-one toolset compatible with +Linux |reg|, macOS |reg| and Windows |reg|, allowing the use of STMicroelectronics proprietary +tools within third-party development environments. + +It notably provides a GDB debugging server (the *ST-LINK GDB Server*) that can be used to debug +applications on STM32 boards thanks to on-board or external ST-LINK debug probes. + +It is compatible with the following debug probes: + +- :ref:`stlink-v21-onboard-debug-probe` +- Standalone `ST-LINK-V2`_, `ST-LINK-V3`_, and `STLINK-V3PWR`_ probes + +Install STM32CubeCLT +-------------------- + +The easiest way to get the ST-LINK GDB Server is to install `STM32CubeCLT`_ from STMicroelectronics' website. +A valid email address is needed to receive the downloading link. + +Basic usage +----------- + +The ST-Link GDB Server can be used through the ``west attach``, ``west debug`` or ``west debugserver`` commands +to debug Zephyr applications. + +.. code-block:: console + + west debug --runner stlink_gdbserver + +.. note:: + + The `STM32CubeProgrammer`_ version contained in the `STM32CubeCLT`_ installation can also be used to flash + applications. To do so, the dedicated :ref:`STM32CubeProgrammer runner ` should + be used instead of ``stlink_gdbserver``, as done in the following example: + + .. code-block:: console + + west flash --runner stm32cubeprogrammer + .. _stm32cubeprog-flash-host-tools: .. _runner_stm32cubeprogrammer: @@ -650,12 +694,12 @@ For more about the UF2 format and its tooling, see `USB Flashing Format (UF2)`_. .. _probe-rs Supported Devices: https://probe.rs/targets/ -.. _STM32CubeProgrammer: - https://www.st.com/en/development-tools/stm32cubeprog.html - .. _STM32CubeCLT: https://www.st.com/en/development-tools/stm32cubeclt.html +.. _STM32CubeProgrammer: + https://www.st.com/en/development-tools/stm32cubeprog.html + .. _STM32CubeProgrammer User Manual: https://www.st.com/resource/en/user_manual/um2237-stm32cubeprogrammer-software-description-stmicroelectronics.pdf diff --git a/doc/develop/flash_debug/probes.rst b/doc/develop/flash_debug/probes.rst index 587df342fe6..999c04bbf0c 100644 --- a/doc/develop/flash_debug/probes.rst +++ b/doc/develop/flash_debug/probes.rst @@ -36,29 +36,29 @@ with pyOCD or OpenOCD debug host tools, or with J-Link firmware to communicate with J-Link debug host tools. -+------------------------------------------+---------------------------------------------------------------------------------------------------------+ -|| *Debug Probes & Host Tools* | Host Tools | -+| *Compatibility Chart* +--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **J-Link Debug** | **OpenOCD** | **pyOCD** | **NXP S32DS** | **NXP LinkServer** | -+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **J-Link External** | ✓ | ✓ | | | | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **LPC-Link2 CMSIS-DAP** | | | | | ✓ | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **LPC-Link2 J-Link** | ✓ | | | | | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **MCU-Link CMSIS-DAP** | | | | | ✓ | -| Debug Probes +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **MCU-Link J-Link** | ✓ | | | | | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **NXP S32 Debug Probe** | | | | ✓ | | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **OpenSDA DAPLink** | | ✓ | ✓ | | ✓ | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **OpenSDA J-Link** | ✓ | | | | | -| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ -| | **ST-LINK/V2-1** | ✓ | ✓ | *some STM32 boards* | | | -+----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+ ++------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------+ +|| *Debug Probes & Host Tools* | Host Tools | ++| *Compatibility Chart* +--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **J-Link Debug** | **OpenOCD** | **pyOCD** | **NXP S32DS** | **NXP LinkServer** | **ST-LINK GDB Server** | ++----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **J-Link External** | ✓ | ✓ | | | | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **LPC-Link2 CMSIS-DAP** | | | | | ✓ | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **LPC-Link2 J-Link** | ✓ | | | | | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **MCU-Link CMSIS-DAP** | | | | | ✓ | | +| Debug Probes +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **MCU-Link J-Link** | ✓ | | | | | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **NXP S32 Debug Probe** | | | | ✓ | | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **OpenSDA DAPLink** | | ✓ | ✓ | | ✓ | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **OpenSDA J-Link** | ✓ | | | | | | +| +-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ +| | **ST-LINK/V2-1** | ✓ | ✓ | *some STM32 boards* | | | ✓ | ++----------------+-------------------------+--------------------+--------------------+---------------------+--------------------+--------------------+------------------------+ Some supported boards in Zephyr do not include an onboard debug probe and @@ -374,6 +374,7 @@ It is compatible with the following host debug tools: - :ref:`openocd-debug-host-tools` - :ref:`jlink-debug-host-tools` +- :ref:`stm32cubeclt-host-tools` For some STM32 based boards, it is also compatible with: diff --git a/scripts/west_commands/runners/__init__.py b/scripts/west_commands/runners/__init__.py index 4c0d7c90ed8..fc318e3d6cc 100644 --- a/scripts/west_commands/runners/__init__.py +++ b/scripts/west_commands/runners/__init__.py @@ -59,6 +59,7 @@ _names = [ 'silabs_commander', 'spi_burn', 'spsdk', + 'stlink_gdbserver', 'stm32cubeprogrammer', 'stm32flash', 'sy1xx', diff --git a/scripts/west_commands/runners/stlink_gdbserver.py b/scripts/west_commands/runners/stlink_gdbserver.py new file mode 100644 index 00000000000..e378cbec84c --- /dev/null +++ b/scripts/west_commands/runners/stlink_gdbserver.py @@ -0,0 +1,188 @@ +# Copyright (c) 2025 STMicroelectronics +# +# SPDX-License-Identifier: Apache-2.0 + +""" +Runner for debugging applications using the ST-LINK GDB server +from STMicroelectronics, provided as part of the STM32CubeCLT. +""" + +import argparse +import platform +import re +import shutil +from pathlib import Path + +from runners.core import MissingProgram, RunnerCaps, RunnerConfig, ZephyrBinaryRunner + +STLINK_GDB_SERVER_DEFAULT_PORT = 61234 + + +class STLinkGDBServerRunner(ZephyrBinaryRunner): + @classmethod + def _get_stm32cubeclt_paths(cls) -> tuple[Path, Path]: + """ + Returns a tuple of two elements of class pathlib.Path: + [0]: path to the ST-LINK_gdbserver executable + [1]: path to the "STM32CubeProgrammer/bin" folder + """ + + def find_highest_clt_version(tools_folder: Path) -> Path | None: + if not tools_folder.is_dir(): + return None + + # List all CubeCLT installations present in tools folder + CUBECLT_FLDR_RE = re.compile(r"stm32cubeclt_([1-9]).(\d+).(\d+)", re.IGNORECASE) + installations: list[tuple[int, Path]] = [] + for f in tools_folder.iterdir(): + m = CUBECLT_FLDR_RE.match(f.name) + if m is not None: + # Compute a number that can be easily compared + # from the STM32CubeCLT version number + major, minor, revis = int(m[1]), int(m[2]), int(m[3]) + ver_num = major * 1000000 + minor * 1000 + revis + installations.append((ver_num, f)) + + if len(installations) == 0: + return None + + # Sort candidates and return the path to the most recent version + most_recent_install = sorted(installations, key=lambda e: e[0], reverse=True)[0] + return most_recent_install[1] + + cur_platform = platform.system() + + # Attempt to find via shutil.which() + if cur_platform in ["Linux", "Windows"]: + gdbserv = shutil.which("ST-LINK_gdbserver") + cubeprg = shutil.which("STM32_Programmer_CLI") + if gdbserv and cubeprg: + # Return the parent of cubeprg as [1] should be the path + # to the folder containing STM32_Programmer_CLI, not the + # path to the executable itself + return (Path(gdbserv), Path(cubeprg).parent) + + # Search in OS-specific paths + search_path: str + tool_suffix = "" + if cur_platform == "Linux": + search_path = "/opt/st/" + elif cur_platform == "Windows": + search_path = "C:\\ST\\" + tool_suffix = ".exe" + elif cur_platform == "Darwin": + search_path = "/opt/ST/" + else: + raise RuntimeError("Unsupported OS") + + clt = find_highest_clt_version(Path(search_path)) + if clt is None: + raise MissingProgram("ST-LINK_gdbserver (from STM32CubeCLT)") + + gdbserver_path = clt / "STLink-gdb-server" / "bin" / f"ST-LINK_gdbserver{tool_suffix}" + cubeprg_bin_path = clt / "STM32CubeProgrammer" / "bin" + + return (gdbserver_path, cubeprg_bin_path) + + @classmethod + def name(cls) -> str: + return "stlink_gdbserver" + + @classmethod + def capabilities(cls) -> RunnerCaps: + return RunnerCaps(commands={"attach", "debug", "debugserver"}, dev_id=True, extload=True) + + @classmethod + def extload_help(cls) -> str: + return "External Loader for ST-Link GDB server" + + @classmethod + def do_add_parser(cls, parser: argparse.ArgumentParser): + # Expose a subset of the ST-LINK GDB server arguments + parser.add_argument( + "--swd", action='store_true', default=True, help="Enable SWD debug mode" + ) + parser.add_argument("--apid", type=int, default=0, help="Target DAP ID") + parser.add_argument( + "--port-number", + type=int, + default=STLINK_GDB_SERVER_DEFAULT_PORT, + help="Port number for GDB client", + ) + + @classmethod + def do_create(cls, cfg: RunnerConfig, args: argparse.Namespace) -> "STLinkGDBServerRunner": + return STLinkGDBServerRunner( + cfg, args.swd, args.apid, args.dev_id, args.port_number, args.extload + ) + + def __init__( + self, + cfg: RunnerConfig, + swd: bool, + ap_id: int | None, + stlink_serial: str | None, + gdb_port: int, + external_loader: str | None, + ): + super().__init__(cfg) + self.ensure_output('elf') + + self._swd = swd + self._gdb_port = gdb_port + self._stlink_serial = stlink_serial + self._ap_id = ap_id + self._external_loader = external_loader + + def do_run(self, command: str, **kwargs): + if command in ["attach", "debug", "debugserver"]: + self.do_attach_debug_debugserver(command) + else: + raise ValueError(f"{command} not supported") + + def do_attach_debug_debugserver(self, command: str): + # self.ensure_output('elf') is called in constructor + # and validated that self.cfg.elf_file is non-null. + # This assertion is required for the test framework, + # which doesn't have this insight - it should never + # trigger in real-world scenarios. + assert self.cfg.elf_file is not None + elf_path = Path(self.cfg.elf_file).as_posix() + + gdb_args = ["-ex", f"target remote :{self._gdb_port}", elf_path] + + (gdbserver_path, cubeprg_path) = STLinkGDBServerRunner._get_stm32cubeclt_paths() + gdbserver_cmd = [gdbserver_path.as_posix()] + gdbserver_cmd += ["--stm32cubeprogrammer-path", str(cubeprg_path.absolute())] + gdbserver_cmd += ["--port-number", str(self._gdb_port)] + gdbserver_cmd += ["--apid", str(self._ap_id)] + gdbserver_cmd += ["--halt"] + + if self._swd: + gdbserver_cmd.append("--swd") + + if command == "attach": + gdbserver_cmd += ["--attach"] + else: # debug/debugserver + gdbserver_cmd += ["--initialize-reset"] + gdb_args += ["-ex", f"load {elf_path}"] + + if self._stlink_serial: + gdbserver_cmd += ["--serial-number", self._stlink_serial] + + if self._external_loader: + extldr_path = cubeprg_path / "ExternalLoader" / self._external_loader + if not extldr_path.exists(): + raise RuntimeError(f"External loader {self._external_loader} does not exist") + gdbserver_cmd += ["--extload", str(extldr_path)] + + self.require(gdbserver_cmd[0]) + + if command == "debugserver": + self.check_call(gdbserver_cmd) + elif self.cfg.gdb is None: # attach/debug + raise RuntimeError("GDB is required for attach/debug") + else: # attach/debug + gdb_cmd = [self.cfg.gdb] + gdb_args + self.require(gdb_cmd[0]) + self.run_server_and_client(gdbserver_cmd, gdb_cmd) diff --git a/scripts/west_commands/tests/test_imports.py b/scripts/west_commands/tests/test_imports.py index 3be15e8d7b9..044a7561e47 100644 --- a/scripts/west_commands/tests/test_imports.py +++ b/scripts/west_commands/tests/test_imports.py @@ -50,6 +50,7 @@ def test_runner_imports(): 'silabs_commander', 'spi_burn', 'spsdk', + 'stlink_gdbserver', 'stm32cubeprogrammer', 'stm32flash', 'sy1xx',