tan-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""`tan probe <identify|read>` -- READ-ONLY J-Link probes (tan-cli#1406).
|
|
3
|
+
|
|
4
|
+
The AEN bench dogfood needed raw `JLinkExe` for two read-only questions; this is
|
|
5
|
+
that, going through the machinery `tan flash --ram` already uses: the trusted
|
|
6
|
+
J-Link binary (`resolve_jlink`), the probe-selection guard
|
|
7
|
+
(`--probe-usb-path` / `--probe-serial`, the `ShowEmuList` TOCTOU check that is
|
|
8
|
+
what the board-farm `JLINK_RUN_PLACE` shim hooks), `flash_cmd._execute` ->
|
|
9
|
+
`_spawn_jlink`, and the `--ram` attach check's own scripts. Nothing is halted,
|
|
10
|
+
written or reset: see `tan.core.probe_plan` for the script rules.
|
|
11
|
+
|
|
12
|
+
Flat verb command (like `tan sdk`): an unknown verb gets a real coded envelope,
|
|
13
|
+
not a parser-level usage error.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, Callable
|
|
23
|
+
|
|
24
|
+
import typer
|
|
25
|
+
|
|
26
|
+
from tan.commands import flash_cmd as fc
|
|
27
|
+
from tan.core import probe_plan as pp
|
|
28
|
+
from tan.core.dp_id import _dp_id_value
|
|
29
|
+
from tan.core.flash_plan import (
|
|
30
|
+
_DEFAULT_JLINK_SPEED,
|
|
31
|
+
FlashPlan,
|
|
32
|
+
FlashPlanError,
|
|
33
|
+
parse_system_manifest,
|
|
34
|
+
fa_int_checked,
|
|
35
|
+
fa_str,
|
|
36
|
+
fa_str_checked,
|
|
37
|
+
validate_identifier,
|
|
38
|
+
)
|
|
39
|
+
from tan.core.global_flags import accept_global_flags
|
|
40
|
+
from tan.core.jlink_binary import ENV_OVERRIDE as JLINK_ENV, resolve_jlink
|
|
41
|
+
from tan.core.ram_run import (
|
|
42
|
+
RamRunError,
|
|
43
|
+
ap_verdict,
|
|
44
|
+
attached_core,
|
|
45
|
+
check_session,
|
|
46
|
+
core_check,
|
|
47
|
+
parse_mem32,
|
|
48
|
+
)
|
|
49
|
+
from tan.envelope import Envelope, Issue
|
|
50
|
+
from tan.exit_codes import ExitCode
|
|
51
|
+
from tan.output_format import FORMAT_HELP, OutputFormat, resolve_format
|
|
52
|
+
|
|
53
|
+
COMMAND = "probe"
|
|
54
|
+
VERBS = ("identify", "read")
|
|
55
|
+
_SCHEMA_VERSION = fc._DATA_SCHEMA_VERSION # the string "1", like every other command
|
|
56
|
+
_SCRIPT_PREFIX = "tan-probe-"
|
|
57
|
+
_GUARD_SCRIPT = (fc._DISABLE_FW_UPDATE + "ShowEmuList\nexit\n").splitlines()
|
|
58
|
+
_Result = tuple[ExitCode, dict[str, Any], list[Issue], list[str]]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class _Run:
|
|
63
|
+
"""Everything one verb needs, resolved once by `_prepare`."""
|
|
64
|
+
|
|
65
|
+
verb: str
|
|
66
|
+
core: str | None
|
|
67
|
+
build_root: str
|
|
68
|
+
exe: str
|
|
69
|
+
pre: list[str]
|
|
70
|
+
argv: tuple[str, ...]
|
|
71
|
+
guard: Any
|
|
72
|
+
echo: dict[str, Any] | None
|
|
73
|
+
expected: str | None
|
|
74
|
+
selected_id: str | None
|
|
75
|
+
report: dict[str, Any]
|
|
76
|
+
issues: list[Issue] = field(default_factory=list)
|
|
77
|
+
log: list[str] = field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _body(verb: str, report: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
81
|
+
return {"schemaVersion": _SCHEMA_VERSION, "verb": verb, **(report or {})}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _refuse(verb, issue, data=None, rc=ExitCode.RUNTIME_FAILURE) -> _Result:
|
|
85
|
+
return rc, _body(verb, data), [issue], [f"probe {verb}: {issue.message}"]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _arg_issue(err: pp.ProbeArgError) -> Issue:
|
|
89
|
+
if err.code == pp.CODE_READ_TOO_LARGE:
|
|
90
|
+
return Issue("probe.read-too-large", "error", str(err))
|
|
91
|
+
return Issue("probe.bad-argument", "error", str(err))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _load_slices(build_root: str) -> tuple[list[tuple[str, dict]], bool, Issue | None]:
|
|
95
|
+
"""`(slices, manifest present, warning)`. An existing but unusable manifest is a
|
|
96
|
+
`probe.manifest-unusable` warning: the comparison it would have armed is skipped."""
|
|
97
|
+
path = fc._abs_join(build_root, "system-manifest.yaml")
|
|
98
|
+
if not fc._is_file(path):
|
|
99
|
+
return [], False, None
|
|
100
|
+
try:
|
|
101
|
+
manifest = parse_system_manifest(fc._read(path))
|
|
102
|
+
except Exception as err: # noqa: BLE001 -- a broken manifest only loses the comparison
|
|
103
|
+
return [], True, Issue(
|
|
104
|
+
"probe.manifest-unusable", "warning",
|
|
105
|
+
f"{path} exists but is not usable ({type(err).__name__}: {err}); the expect_dpidr "
|
|
106
|
+
"comparison and the manifest's probe settings were skipped",
|
|
107
|
+
)
|
|
108
|
+
return [(sl.core_id, sl.flash_args) for sl in manifest.slices], True, None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _attach_params(flash_args: dict[str, Any], jlink_report: dict[str, Any]):
|
|
112
|
+
"""`(preamble, speed, expect_dpidr)` from the selected slice's flash_args; echoes a
|
|
113
|
+
`jlink_device` substitution into the report."""
|
|
114
|
+
serial = fa_str_checked(flash_args, "jlink_serial", False)
|
|
115
|
+
wanted = fa_str(flash_args, "jlink_device") or pp.DEFAULT_DEVICE
|
|
116
|
+
validate_identifier(wanted, "jlink_device", destination="a J-Link Commander script line")
|
|
117
|
+
device = wanted if wanted.startswith("Cortex-") else pp.DEFAULT_DEVICE
|
|
118
|
+
jlink_report["device"] = device
|
|
119
|
+
if device != wanted: # the part profile is a flash loader, not a read attach
|
|
120
|
+
jlink_report["deviceSubstitutedFrom"] = wanted
|
|
121
|
+
speed = fa_int_checked(flash_args, "jlink_speed") or _DEFAULT_JLINK_SPEED
|
|
122
|
+
if serial is not None:
|
|
123
|
+
validate_identifier(serial, "jlink_serial", destination=fc._JLINK_SERIAL_DESTINATION)
|
|
124
|
+
return pp.make_preamble(serial, speed, device), speed, fa_str(flash_args, "expect_dpidr")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _prepare(
|
|
128
|
+
verb, core, build_root, probe_serial, probe_usb_path, jlink_path, project_dir, enumerate_probes
|
|
129
|
+
) -> _Run | _Result:
|
|
130
|
+
slices, present, warning = _load_slices(build_root)
|
|
131
|
+
flash_args, selected_id, ambiguity, note = pp.select_slice(slices, core)
|
|
132
|
+
report: dict[str, Any] = {
|
|
133
|
+
"core": core, "buildRoot": build_root, "writes": False, "manifestPresent": present,
|
|
134
|
+
}
|
|
135
|
+
warn = [warning] if warning else []
|
|
136
|
+
if note:
|
|
137
|
+
warn.append(Issue("probe.no-manifest", "info", note))
|
|
138
|
+
if ambiguity:
|
|
139
|
+
return _refuse(verb, Issue("probe.failed", "error", ambiguity), report)
|
|
140
|
+
found = resolve_jlink(jlink_path, project_dir=project_dir)
|
|
141
|
+
exe = found.path if found is not None else None
|
|
142
|
+
report["jlink"] = {"binary": exe, "binarySource": found.source if found else None}
|
|
143
|
+
if exe is None:
|
|
144
|
+
return _refuse(verb, Issue("probe.failed", "error", fc._NO_TRUSTED_JLINK), report)
|
|
145
|
+
ctx = fc._Context(
|
|
146
|
+
sku="", build_root=build_root, sdk_root="", dry_run=False, skip_missing_tools=False,
|
|
147
|
+
force_confirm=False, capture=True, probe_serial=probe_serial,
|
|
148
|
+
probe_usb_path=probe_usb_path, project_dir=project_dir, jlink_path=jlink_path,
|
|
149
|
+
**({"enumerate_probes": enumerate_probes} if enumerate_probes is not None else {}),
|
|
150
|
+
)
|
|
151
|
+
try:
|
|
152
|
+
flash_args, selection, snapshot = fc._flow_d_probe_selection(flash_args, ctx)
|
|
153
|
+
echo = fc._probe_echo(selection, ctx)
|
|
154
|
+
if selection is not None and selection.refusal_code is not None:
|
|
155
|
+
data = {**report, **({"probe": echo} if echo else {})}
|
|
156
|
+
issue = Issue(f"flash.probe-{selection.refusal_code}", "error", selection.refusal or "")
|
|
157
|
+
return _refuse(verb, issue, data)
|
|
158
|
+
guard = None
|
|
159
|
+
if selection is not None and (selection.serial is not None or selection.visible is None):
|
|
160
|
+
guard = fc._ProbeGuard(selection, snapshot, ctx.enumerate_probes, echo,
|
|
161
|
+
script_prefix=_SCRIPT_PREFIX)
|
|
162
|
+
pre, speed, expected = _attach_params(flash_args, report["jlink"])
|
|
163
|
+
except (FlashPlanError, RamRunError) as err:
|
|
164
|
+
return _refuse(verb, Issue("probe.failed", "error", str(err)), report)
|
|
165
|
+
if echo:
|
|
166
|
+
report["probe"] = echo
|
|
167
|
+
report["guard"] = {"script": _GUARD_SCRIPT, "runsBeforeEachSession": guard is not None,
|
|
168
|
+
"note": "the probe-pin verification, spawned before each session below"}
|
|
169
|
+
argv = ("JLinkExe", "-device", report["jlink"]["device"], "-if", "SWD", "-speed", str(speed),
|
|
170
|
+
"-NoGui", "1",
|
|
171
|
+
"-CommanderScript")
|
|
172
|
+
run = _Run(verb, core, build_root, exe, pre, argv, guard, echo, expected, selected_id, report)
|
|
173
|
+
run.issues.extend(warn)
|
|
174
|
+
return run
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _session(run: _Run, label: str, script: str) -> tuple[str, str | None]:
|
|
178
|
+
"""Spawn one read-only session. `(transcript, problem)`; the text sent and the
|
|
179
|
+
output go to the run's transcript."""
|
|
180
|
+
sent = fc._DISABLE_FW_UPDATE + script
|
|
181
|
+
out = fc._execute(
|
|
182
|
+
FlashPlan(argv=run.argv, ok_message="", jlink_script=script),
|
|
183
|
+
True, None, None, run.guard, jlink_exe=run.exe, script_prefix=_SCRIPT_PREFIX,
|
|
184
|
+
)
|
|
185
|
+
text = f"{out.stdout}\n{out.stderr}"
|
|
186
|
+
run.log.append(f"## {label} script (exactly as sent)\n{sent}\n## stdout\n{out.stdout}\n"
|
|
187
|
+
f"## stderr\n{out.stderr}\n")
|
|
188
|
+
run.report.setdefault("scripts", {})[label] = sent.splitlines()
|
|
189
|
+
if run.guard is not None and run.guard.tripped:
|
|
190
|
+
return text, run.guard.tripped
|
|
191
|
+
problem = None if out.success else (fc._capture_tail(out) or "J-Link failed")
|
|
192
|
+
return text, problem or check_session(text, loadbin=False)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _cache_log_dir() -> str:
|
|
196
|
+
"""One stable directory for transcripts when there is no build root, so nothing
|
|
197
|
+
accumulates in fresh temp dirs: `$XDG_CACHE_HOME/tan/probe-logs`, else
|
|
198
|
+
`~/.cache/tan/probe-logs` (`_write_transcript` prunes the oldest)."""
|
|
199
|
+
root = os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache")
|
|
200
|
+
return os.path.join(root, "tan", "probe-logs")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _save_log(run: _Run) -> None:
|
|
204
|
+
stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
|
|
205
|
+
name = re.sub(r"[^A-Za-z0-9._-]", "_", f"probe-{run.verb}-{stamp}.log")
|
|
206
|
+
base = os.path.join(run.build_root, "flash-logs") if os.path.isdir(run.build_root) else None
|
|
207
|
+
try:
|
|
208
|
+
directory = base or _cache_log_dir()
|
|
209
|
+
run.report["transcriptPath"] = fc._write_transcript(
|
|
210
|
+
os.path.join(directory, name), f"# tan probe {run.verb}\n" + "".join(run.log)
|
|
211
|
+
)
|
|
212
|
+
except OSError as err:
|
|
213
|
+
run.report["transcriptPath"] = None
|
|
214
|
+
run.report["transcriptError"] = str(err)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _finish(run: _Run, rc: ExitCode, lines: list[str], *more: Issue) -> _Result:
|
|
218
|
+
if run.echo is not None and "identity" in run.report:
|
|
219
|
+
run.report["identity"]["isolation"] = run.echo.get("isolation")
|
|
220
|
+
run.issues.extend(more)
|
|
221
|
+
_save_log(run)
|
|
222
|
+
return rc, _body(run.verb, run.report), run.issues, lines
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _fail(run: _Run, message: str, *, read: bool = False) -> _Result:
|
|
226
|
+
if run.guard is not None and run.guard.tripped:
|
|
227
|
+
issue = Issue(f"flash.probe-{run.guard.tripped_code}", "error", run.guard.tripped)
|
|
228
|
+
else:
|
|
229
|
+
issue = (Issue("probe.read-failed", "error", message) if read
|
|
230
|
+
else Issue("probe.failed", "error", message))
|
|
231
|
+
return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe {run.verb}: {issue.message}"], issue)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _dpidr_issue(state: str, expected: str | None, dpidr: str | None) -> Issue:
|
|
235
|
+
if state == "unread":
|
|
236
|
+
return Issue("probe.dpidr-unread", "error", "the connect banner reported no SW-DP ID")
|
|
237
|
+
return Issue(
|
|
238
|
+
"probe.dpidr-mismatch", "error",
|
|
239
|
+
f"expected SW-DP IDR {expected} (manifest expect_dpidr) but the probe reported "
|
|
240
|
+
f"{dpidr}: a different board is attached",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _core_issue(message: str) -> Issue:
|
|
245
|
+
return Issue("probe.core-mismatch", "error", message)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _identify(run: _Run) -> _Result:
|
|
249
|
+
text, problem = _session(run, "dpidr", pp.identity_script(run.pre))
|
|
250
|
+
if problem:
|
|
251
|
+
return _fail(run, f"the DPIDR read failed: {problem}")
|
|
252
|
+
dpidr, att = _dp_id_value(text), attached_core(text)
|
|
253
|
+
verdict = ap_verdict(att)
|
|
254
|
+
state = pp.dpidr_state(run.expected, dpidr) or ("unread" if dpidr is None else None)
|
|
255
|
+
ident = {
|
|
256
|
+
"dpidr": dpidr, "expectedDpidr": run.expected,
|
|
257
|
+
"dpidrMatch": None if not run.expected or dpidr is None else state is None,
|
|
258
|
+
"apAddr": (att or {}).get("apAddr"), "cpuid": (att or {}).get("cpuid"),
|
|
259
|
+
"core": verdict, "itcmVerdict": "not-checked", "apVerdict": verdict,
|
|
260
|
+
"attached": att, "isolation": None,
|
|
261
|
+
}
|
|
262
|
+
run.report["identity"] = ident
|
|
263
|
+
if state: # C: nothing else runs against a board that is not the expected one
|
|
264
|
+
issue = _dpidr_issue(state, run.expected, dpidr)
|
|
265
|
+
return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe identify: {issue.message}"], issue)
|
|
266
|
+
if pp.itcm_check_allowed(pp.is_he_target(run.core, run.selected_id), verdict):
|
|
267
|
+
text2, problem = _session(run, "coreCheck", pp.core_script(run.pre))
|
|
268
|
+
if problem:
|
|
269
|
+
return _fail(run, f"the core check failed: {problem}")
|
|
270
|
+
verdict, evidence = core_check(text2)
|
|
271
|
+
ident.update(core=verdict, itcmVerdict=evidence["itcmVerdict"],
|
|
272
|
+
apVerdict=evidence["apVerdict"], attached=evidence.get("ap") or att)
|
|
273
|
+
ident["apAddr"] = (evidence.get("ap") or att or {}).get("apAddr")
|
|
274
|
+
ident["cpuid"] = (evidence.get("ap") or att or {}).get("cpuid")
|
|
275
|
+
extra: list[Issue] = []
|
|
276
|
+
if ident["itcmVerdict"] == "not-checked":
|
|
277
|
+
extra.append(Issue("probe.itcm-not-checked", "info",
|
|
278
|
+
"ITCM corroboration skipped: "
|
|
279
|
+
+ pp.itcm_skip_reason(run.core, run.selected_id, verdict)))
|
|
280
|
+
clash = pp.core_contradiction(pp.claimed_core(run.core, run.selected_id), verdict)
|
|
281
|
+
if clash:
|
|
282
|
+
extra.append(_core_issue(clash))
|
|
283
|
+
if not run.expected and not any(i.code == "probe.no-manifest" for i in run.issues):
|
|
284
|
+
why = "no system-manifest.yaml" if not run.report["manifestPresent"] else "no expect_dpidr armed"
|
|
285
|
+
extra.append(Issue("probe.no-manifest", "info",
|
|
286
|
+
f"{why} for the selected core; the DPIDR match was skipped"))
|
|
287
|
+
rc = ExitCode.RUNTIME_FAILURE if clash else ExitCode.SUCCESS
|
|
288
|
+
line = (f"probe identify: dpidr={dpidr} core={verdict} ap={ident['apAddr']} "
|
|
289
|
+
f"cpuid={ident['cpuid']}")
|
|
290
|
+
return _finish(run, rc, [line], *extra)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _read(run: _Run, addr: int, words: int) -> _Result:
|
|
294
|
+
text, problem = _session(run, "read", pp.read_script(run.pre, addr, words))
|
|
295
|
+
if problem:
|
|
296
|
+
return _fail(run, f"the read failed: {problem}", read=True)
|
|
297
|
+
dpidr, att = _dp_id_value(text), attached_core(text)
|
|
298
|
+
run.report["attached"] = {"dpidr": dpidr, "ap": att}
|
|
299
|
+
state = pp.dpidr_state(run.expected, dpidr)
|
|
300
|
+
clash = pp.core_contradiction(pp.claimed_core(run.core, run.selected_id), ap_verdict(att))
|
|
301
|
+
bad = _dpidr_issue(state, run.expected, dpidr) if state else (_core_issue(clash) if clash else None)
|
|
302
|
+
if bad: # the words may belong to another board or core: not returned
|
|
303
|
+
return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe read: {bad.message}"], bad)
|
|
304
|
+
got = parse_mem32(text, addr, words)
|
|
305
|
+
if got is None:
|
|
306
|
+
return _fail(run, f"J-Link returned no complete dump for {words} words at 0x{addr:08X}",
|
|
307
|
+
read=True)
|
|
308
|
+
run.report["read"] = {
|
|
309
|
+
"address": f"0x{addr:08X}", "words": words, "bytes": 4 * words,
|
|
310
|
+
"data": [f"0x{w:08X}" for w in got], "attached": att,
|
|
311
|
+
}
|
|
312
|
+
lines = [f"probe read: 0x{addr + 4 * i:08X} = 0x{w:08X}" for i, w in enumerate(got)]
|
|
313
|
+
return _finish(run, ExitCode.SUCCESS, lines)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _run(
|
|
317
|
+
verb: str,
|
|
318
|
+
address: str | None,
|
|
319
|
+
count: str | None,
|
|
320
|
+
core: str | None,
|
|
321
|
+
build_root: str,
|
|
322
|
+
probe_serial: str | None,
|
|
323
|
+
probe_usb_path: str | None,
|
|
324
|
+
jlink_path: str | None,
|
|
325
|
+
project_dir: str,
|
|
326
|
+
enumerate_probes: Callable[[], Any] | None = None,
|
|
327
|
+
) -> _Result:
|
|
328
|
+
bad = ExitCode.VALIDATION_FAILURE
|
|
329
|
+
if verb not in VERBS:
|
|
330
|
+
text = f"unknown verb {verb!r}; use `tan probe identify` or `tan probe read <addr> [words]`"
|
|
331
|
+
return _refuse(verb, Issue("probe.unknown-verb", "error", text), rc=bad)
|
|
332
|
+
addr = words = None
|
|
333
|
+
try:
|
|
334
|
+
core = pp.normalise_core(core)
|
|
335
|
+
if verb == "read":
|
|
336
|
+
if address is None:
|
|
337
|
+
raise pp.ProbeArgError(pp.CODE_BAD_ARGUMENT, "`tan probe read` needs an address")
|
|
338
|
+
addr, words = pp.parse_read(address, count)
|
|
339
|
+
elif address is not None or count is not None:
|
|
340
|
+
raise pp.ProbeArgError(pp.CODE_BAD_ARGUMENT, "`tan probe identify` takes no address")
|
|
341
|
+
except pp.ProbeArgError as err:
|
|
342
|
+
return _refuse(verb, _arg_issue(err), rc=bad)
|
|
343
|
+
message = pp.window_refusal(addr, words) if verb == "read" else None
|
|
344
|
+
if message: # every core, no J-Link spawned
|
|
345
|
+
return _refuse(verb, Issue("probe.read-unsafe-region", "error", message), rc=bad)
|
|
346
|
+
run = _prepare(verb, core, build_root, probe_serial, probe_usb_path, jlink_path,
|
|
347
|
+
project_dir, enumerate_probes)
|
|
348
|
+
if not isinstance(run, _Run):
|
|
349
|
+
return run
|
|
350
|
+
return _identify(run) if verb == "identify" else _read(run, addr, words)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def probe(
|
|
354
|
+
ctx: typer.Context,
|
|
355
|
+
verb: str = typer.Argument(..., metavar="VERB", help="`identify` or `read`."),
|
|
356
|
+
address: str = typer.Argument(
|
|
357
|
+
None, metavar="[ADDR]", help="read: first word address, plain hex (0x...) or decimal, "
|
|
358
|
+
"4-byte aligned."),
|
|
359
|
+
count: str = typer.Argument(
|
|
360
|
+
None, metavar="[WORDS]", help="read: 32-bit words to read (default 4, at most 256; "
|
|
361
|
+
"more is probe.read-too-large)."),
|
|
362
|
+
project: str = typer.Option(None, "--project", metavar="PATH", help="Project root (defaults to '.')."),
|
|
363
|
+
build_root: str = typer.Option(
|
|
364
|
+
None, "--build-root", metavar="PATH",
|
|
365
|
+
help="Build root holding system-manifest.yaml (default <project>/build). When present, its "
|
|
366
|
+
"slice's flash_args supply expect_dpidr (compared by identify), jlink_serial/jlink_speed."),
|
|
367
|
+
core: str = typer.Option(
|
|
368
|
+
None, "--core", metavar="m55_he|m55_hp",
|
|
369
|
+
help="The core you mean to probe; also picks the manifest slice. identify checks the "
|
|
370
|
+
"attach against it (probe.core-mismatch) and reads the ITCM windows only for m55_he. "
|
|
371
|
+
"read refuses 0x50000000-0x5FFFFFFF on EVERY core (probe.read-unsafe-region)."),
|
|
372
|
+
probe_serial: str = typer.Option(None, "--probe-serial", metavar="SN", help="J-Link serial for this run."),
|
|
373
|
+
probe_usb_path: str = typer.Option(
|
|
374
|
+
None, "--probe-usb-path", metavar="BUS-PORT",
|
|
375
|
+
help="Select the J-Link at this USB port path (e.g. 3-4.2); verified before every JLinkExe "
|
|
376
|
+
"spawn exactly as `tan flash` does (TAN_PROBE_USB_PATH is exported for a masking wrapper)."),
|
|
377
|
+
jlink: str = typer.Option(
|
|
378
|
+
None, "--jlink", metavar="PATH",
|
|
379
|
+
help=f"The J-Link Commander binary (otherwise {JLINK_ENV}, PATH, then a SEGGER install root; "
|
|
380
|
+
"never the project .venv)."),
|
|
381
|
+
output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
|
|
382
|
+
) -> None:
|
|
383
|
+
"""READ-ONLY J-Link probe: `identify` (SW-DP ID, access port, CPUID, core verdict) and
|
|
384
|
+
`read ADDR [WORDS]` (a bounded `mem32`). Never writes, erases, halts, resets or runs.
|
|
385
|
+
|
|
386
|
+
\b
|
|
387
|
+
tan probe identify --probe-usb-path 3-4.2 --core m55_he --format json
|
|
388
|
+
tan probe read 0x80010000 4 --probe-usb-path 3-4.2
|
|
389
|
+
"""
|
|
390
|
+
json_mode = resolve_format(output_format, ctx.obj, choices=OutputFormat) == "json"
|
|
391
|
+
probe_serial = probe_serial if isinstance(probe_serial, str) else None
|
|
392
|
+
probe_usb_path = probe_usb_path if isinstance(probe_usb_path, str) else None
|
|
393
|
+
if probe_usb_path is not None and not fc.is_valid_usb_path(probe_usb_path):
|
|
394
|
+
raise typer.BadParameter(
|
|
395
|
+
f"{probe_usb_path!r} is not a USB port path like 3-4.2 (<bus>-<port>[.<port>...])",
|
|
396
|
+
param_hint="--probe-usb-path",
|
|
397
|
+
)
|
|
398
|
+
cwd = fc.workspace_root(project)
|
|
399
|
+
project_obj = fc._resolve_project(cwd, None)
|
|
400
|
+
root = build_root if isinstance(build_root, str) else fc._abs_join(cwd, "build")
|
|
401
|
+
if isinstance(build_root, str) and not root.startswith("/"):
|
|
402
|
+
root = fc._abs_join(cwd, root)
|
|
403
|
+
try:
|
|
404
|
+
exit_code, data, issues, lines = _run(
|
|
405
|
+
verb, address, count, core if isinstance(core, str) else None, root,
|
|
406
|
+
probe_serial, probe_usb_path, jlink if isinstance(jlink, str) else None, cwd,
|
|
407
|
+
)
|
|
408
|
+
except Exception as err: # noqa: BLE001 -- a tan bug is reported as one, with an envelope
|
|
409
|
+
exit_code = ExitCode.INTERNAL_FAILURE
|
|
410
|
+
data = {"schemaVersion": _SCHEMA_VERSION, "verb": verb}
|
|
411
|
+
issues = [Issue("probe.internal-failure", "error", f"{type(err).__name__}: {err}")]
|
|
412
|
+
lines = ["probe: internal failure"]
|
|
413
|
+
if json_mode:
|
|
414
|
+
fc.emit(Envelope(COMMAND, project_obj, data, issues, exit_code))
|
|
415
|
+
else:
|
|
416
|
+
for line in lines:
|
|
417
|
+
print(line, file=sys.stderr if exit_code else sys.stdout)
|
|
418
|
+
raise typer.Exit(int(exit_code))
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
probe = accept_global_flags(probe)
|