blob: 4b0996d79b9d5151f5d8ce7e0f7ce464194ebf6c [file] [log] [blame]
#
# Copyright (c) Memfault, Inc.
# See LICENSE for details
#
from __future__ import annotations
import os
import re
import shutil
import sys
from typing import TYPE_CHECKING
from invoke import Collection, task
from .gdb import gdb_build_cmd
if TYPE_CHECKING:
from tasks.lib.invoke_utils import Context
TASKS_DIR = os.path.dirname(__file__)
MEMFAULT_SDK_ROOT = os.path.join(TASKS_DIR, "..")
ESP32_PLATFORM_ROOT = os.path.join(MEMFAULT_SDK_ROOT, "examples", "esp32")
ESP32_IDF_ROOT = os.path.join(ESP32_PLATFORM_ROOT, "esp-idf")
ESP32_IDF_SCRIPT = os.path.join(ESP32_IDF_ROOT, "tools", "idf.py")
ESP32_COREDUMP_SCRIPT = os.path.join(ESP32_IDF_ROOT, "components", "espcoredump", "espcoredump.py")
ESP32_TEST_APP_ROOT = os.path.join(ESP32_PLATFORM_ROOT, "apps", "memfault_demo_app")
ESP32_TEST_APP_ELF = os.path.join(ESP32_TEST_APP_ROOT, "build", "memfault-esp32-demo-app.elf")
ESP32_FTDI_VID_PID = [(0x0403, 0x6010)]
OPENOCD_GDB_PORT_DEFAULT = 3333
def _run_idf_script(ctx: "Context", *args: str, **kwargs: object) -> None:
# allow selecting a specific python interpreter instead of the active one.
# this is necessary in CI, because the mbed build task modifies the python
# environment 😖, and idf.py runs a pkg_resources.require() which fails if
# the mbed task modified any deps in an incompatible way. it's better to
# keep the esp_idf environment isolated and just use it to run idf.py
python = os.getenv("ESP_IDF_PYTHON", sys.executable)
with ctx.cd(ESP32_TEST_APP_ROOT):
ctx.run(
"{python} {idf} {args}".format(
python=python, idf=ESP32_IDF_SCRIPT, args=" ".join(args)
),
env={
"IDF_PATH": ESP32_IDF_ROOT,
# newer versions of ESP-IDF require this too (it's much easier
# when we can just do '. export.sh' instead!). provide a
# reasonable default guess if unset.
"ESP_ROM_ELF_DIR": os.getenv(
"ESP_ROM_ELF_DIR", "~/.espressif/tools/esp-rom-elfs/20241011/"
),
},
**kwargs,
)
@task
def run_xtensa_toolchain_check(ctx: "Context") -> None:
if sys.version_info.major < 3:
# shutil which is only available for python3
return
xtensa_toolchain = shutil.which("xtensa-esp32-elf-gcc")
if xtensa_toolchain is None:
msg = (
"Couldn't find esp32 toolchain. Currently using the toolchain which can be"
" found here {}".format(
"https://docs.espressif.com/projects/esp-idf/en/v3.1/get-started/index.html#setup-toolchain"
)
)
raise FileNotFoundError(msg)
@task(pre=[run_xtensa_toolchain_check])
def esp32_app_build(ctx: "Context") -> None:
"""Build the ESP32 test app"""
_run_idf_script(ctx, "build")
@task
def esp32_app_clean(ctx: "Context") -> None:
"""Clean the ESP32 test app"""
_run_idf_script(ctx, "fullclean")
@task(pre=[run_xtensa_toolchain_check])
def esp32s2_app_build(ctx: "Context") -> None:
"""Build the ESP32-S2 test app"""
# !NOTE! 'set-target' was added in ESP-IDF v4.1. If you are using an older
# version of ESP-IDF, building for the ESP32-S2 + ESP32-S3 won't work.
_run_idf_script(ctx, "set-target esp32s2")
_run_idf_script(ctx, "build")
@task(pre=[run_xtensa_toolchain_check])
def esp32s3_app_build(ctx: "Context") -> None:
"""Build the ESP32-S3 test app"""
_run_idf_script(ctx, "set-target esp32s3")
_run_idf_script(ctx, "build")
@task
def esp32_app_flash(ctx: "Context") -> None:
"""Flash the ESP32 test app"""
_run_idf_script(ctx, "flash")
@task
def esp32_console(ctx: "Context") -> None:
"""Flash the ESP32 test app"""
_run_idf_script(ctx, "monitor")
@task
def esp32_app_menuconfig(ctx: "Context") -> None:
"""Run menuconfig for the ESP32 test app"""
_run_idf_script(ctx, "menuconfig", pty=True)
@task
def esp32_openocd(ctx: "Context") -> None:
"""Launch openocd"""
if "ESP32_OPENOCD" not in os.environ:
print("Set ESP32_OPENOCD environment variable to point to openocd-esp32 root directory!")
print(
"Download the openocd-esp32 binaries here:"
" https://github.com/espressif/openocd-esp32/releases"
)
sys.exit(-1)
with ctx.cd(os.environ["ESP32_OPENOCD"]):
ctx.run(
"bin/openocd -s share/openocd/scripts "
"-f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg",
pty=True,
)
@task
def esp32_app_gdb(ctx: "Context", gdb: int | None = None, reset: bool = False) -> None:
"""Launches xtensa-gdb with app elf and connects to openocd gdb server"""
if gdb is None:
gdb = OPENOCD_GDB_PORT_DEFAULT
with ctx.cd(ESP32_TEST_APP_ROOT):
gdb_cmd = gdb_build_cmd(
"", ESP32_TEST_APP_ELF, gdb, gdb_prefix="xtensa-esp32-elf-", reset=reset
)
ctx.run(gdb_cmd, pty=True)
@task
def esp32_decode_backtrace(
ctx: "Context", backtrace_str: str, symbol_file: str, verbose: bool = False
) -> None:
"""Decode a backtrace emitted by ESP-IDF panic handling
The backtrace_str should be passed as a string of separated address pairs
where each pair has the format "pc:sp". For example:
"0x40081cda:0x3ffd00a0 0x40082ce3:0x3ffd00c0 0x4008927d:0x3ffd00e0"
This backtrace is printed by the ESP-IDF debug helpers here:
https://github.com/espressif/esp-idf/blob/v5.4.2/components/esp_system/port/arch/xtensa/debug_helpers.c
Note: `idf.py monitor` will automatically decode the backtrace for you
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/idf-monitor.html#automatic-address-decoding
so this task is primarily used if only the raw backtrace is supplied (e.g. via support ticket or logs).
"""
addr_tuples = [
(int(pc, 16), int(sp, 16))
for pc, sp in re.findall(r"0x([0-9a-fA-F]+):0x([0-9a-fA-F]+)", backtrace_str)
]
for i in range(len(addr_tuples)):
result = ctx.run(
"addr2line -e {} {address:x}".format(symbol_file, address=addr_tuples[i][0]),
hide=True,
warn=True,
)
if result.ok:
print("Frame {frame:>2}: {line}".format(frame=i, line=result.stdout.strip()))
if verbose:
print(" PC = 0x{address:x}".format(address=addr_tuples[i][0]))
print(" SP = 0x{sp:x}".format(sp=addr_tuples[i][1]))
else:
print(
"Error processing 0x{address:x}: {error}".format(
address=addr_tuples[i][0], error=result.stderr.strip()
)
)
ns = Collection("esp32")
ns.add_task(esp32_console, name="console")
ns.add_task(esp32_openocd, name="gdbserver")
ns.add_task(esp32_app_build, name="build")
ns.add_task(esp32s2_app_build, name="build-s2")
ns.add_task(esp32s3_app_build, name="build-s3")
ns.add_task(esp32_app_clean, name="clean")
ns.add_task(esp32_app_flash, name="flash")
ns.add_task(esp32_app_gdb, name="app-gdb")
ns.add_task(esp32_app_menuconfig, name="app-menuconfig")
ns.add_task(esp32_decode_backtrace, name="decode-backtrace")