@typecad/framework-zephyr 1.0.0-alpha.15 → 1.0.0-alpha.16
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.
- package/dist/toolchain/debug-config.js +15 -5
- package/dist/toolchain/index.d.ts +49 -0
- package/dist/toolchain/index.js +49 -20
- package/package.json +4 -4
- package/src/toolchain/debug-config.ts +15 -5
- package/src/toolchain/index.ts +85 -12
|
@@ -333,14 +333,24 @@ function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel, method) {
|
|
|
333
333
|
// thb (the first user Continue stops at main()) but hands the run/stop
|
|
334
334
|
// transition to cortex-debug.
|
|
335
335
|
//
|
|
336
|
-
// Paths
|
|
337
|
-
//
|
|
338
|
-
|
|
336
|
+
// Paths in GDB commands must be RELATIVE (resolved against GDB's own
|
|
337
|
+
// working directory), never ${workspaceFolder} literals or absolute paths:
|
|
338
|
+
// - cortex-debug passes command strings through verbatim — no variable
|
|
339
|
+
// expansion in postAttachCommands (verified against its gdb.ts) — so a
|
|
340
|
+
// ${workspaceFolder} would reach GDB as a literal.
|
|
341
|
+
// - Even where VS Code expands it, on Windows the expansion carries
|
|
342
|
+
// backslashes that GDB reads as escape sequences (\t → tab, etc.).
|
|
343
|
+
// - An absolute path bakes the generating machine's checkout location
|
|
344
|
+
// into launch.json and breaks on every other clone/checkout.
|
|
345
|
+
// cortex-debug spawns GDB with cwd from the config's `cwd` field
|
|
346
|
+
// (${workspaceFolder} above), so a relative path IS the workspace root —
|
|
347
|
+
// `set directories .` adds exactly the directory the old absolute form did,
|
|
348
|
+
// and `source <rel>` finds the frame-filter script.
|
|
339
349
|
// The Xtensa flash-mapping commands are ESP32-specific (app flash isn't
|
|
340
350
|
// readable until the bootloader maps it); ARM targets drop them.
|
|
341
351
|
const isEsp32Target = o.target.split('/')[0].startsWith('esp32');
|
|
342
352
|
const postAttachCommands = [
|
|
343
|
-
|
|
353
|
+
'set directories .',
|
|
344
354
|
'set remote hardware-watchpoint-limit 2',
|
|
345
355
|
'set remote hardware-breakpoint-limit 2',
|
|
346
356
|
...(isEsp32Target ? [
|
|
@@ -352,7 +362,7 @@ function buildLaunchConfig(o, gdbScriptRel, openOcdCfgRel, method) {
|
|
|
352
362
|
...(isEsp32Target ? ['c'] : []),
|
|
353
363
|
];
|
|
354
364
|
if (gdbScriptRel) {
|
|
355
|
-
postAttachCommands.splice(1, 0, `source ${
|
|
365
|
+
postAttachCommands.splice(1, 0, `source ${gdbScriptRel}`);
|
|
356
366
|
}
|
|
357
367
|
// Probe-method-driven server shape: jlink-runner methods use cortex-debug's
|
|
358
368
|
// jlink server (needs the device name from the board table); openocd-runner
|
|
@@ -79,6 +79,45 @@ export type ProbeResolution = {
|
|
|
79
79
|
error: string;
|
|
80
80
|
};
|
|
81
81
|
export declare function resolveProbeMethod(zc: Record<string, unknown> | undefined, chip: ZephyrChipDescriptor, purpose?: 'flash' | 'debug'): ProbeResolution;
|
|
82
|
+
/**
|
|
83
|
+
* Run an openocd session against the board's probe config — the shared
|
|
84
|
+
* engine for the pre-flash quiesce and the post-flash SYSRESETREQ (see the
|
|
85
|
+
* call sites in upload()). The config is the probe method's verbatim
|
|
86
|
+
* debugCfg from the board catalog (the same lines `west debug` uses);
|
|
87
|
+
* `commands` are appended after `-f <cfg> -c init`. Returns a flash note on
|
|
88
|
+
* success, undefined when skipped or failed (best-effort by design).
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the openocd binary (plus script search dirs) for the probe session.
|
|
92
|
+
*
|
|
93
|
+
* Resolution order mirrors how west's own openocd runner finds the binary, so
|
|
94
|
+
* the session and `west flash` drive the SAME openocd:
|
|
95
|
+
* 1. $OPENOCD — the variable Zephyr's CMake reads into the build cache.
|
|
96
|
+
* 2. A Zephyr-SDK-hosted install ($ZEPHYR_SDK_INSTALL_DIR, else the
|
|
97
|
+
* discovered west install's SDK). Layout differs by SDK generation —
|
|
98
|
+
* hosttools/openocd/share/openocd/scripts vs hosttools/openocd/scripts —
|
|
99
|
+
* so both script dirs are collected.
|
|
100
|
+
* 3. PATH — Linux distro / conda / micromamba installs (a micromamba-env
|
|
101
|
+
* openocd is the common Linux setup; the SDK layout check alone made the
|
|
102
|
+
* deterministic probe session unreachable there, silently dropping every
|
|
103
|
+
* Linux flash to the racy `west flash` fallback).
|
|
104
|
+
*
|
|
105
|
+
* A non-SDK binary resolves with no explicit -s dirs: it finds its own
|
|
106
|
+
* interface/target scripts via its compiled-in search path. Returns undefined
|
|
107
|
+
* when no openocd can be found (the caller then uses the west fallback).
|
|
108
|
+
*
|
|
109
|
+
* Exported (pure) so the resolution contract is unit-testable without
|
|
110
|
+
* spawning openocd.
|
|
111
|
+
*/
|
|
112
|
+
export interface SessionOpenOcd {
|
|
113
|
+
readonly exe: string;
|
|
114
|
+
readonly searchDirs: readonly string[];
|
|
115
|
+
}
|
|
116
|
+
export declare function resolveSessionOpenOcd(env?: {
|
|
117
|
+
OPENOCD?: string | undefined;
|
|
118
|
+
ZEPHYR_SDK_INSTALL_DIR?: string | undefined;
|
|
119
|
+
PATH?: string | undefined;
|
|
120
|
+
}, sdkInstallDir?: string): SessionOpenOcd | undefined;
|
|
82
121
|
/**
|
|
83
122
|
* Whether a flash runner carries the upload over a serial port. Runner-gated,
|
|
84
123
|
* never board-name-gated: esptool and bossac are the only runners
|
|
@@ -111,6 +150,16 @@ export declare function buildFlashArgs(buildDir: string, userRunner: string | un
|
|
|
111
150
|
* Exported (pure) so the classification is unit-testable without spawning west.
|
|
112
151
|
*/
|
|
113
152
|
export declare function classifyUploadResult(runner: string | undefined, status: number | null, output: string): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Whether a `west flash` (openocd) output carries one of the known
|
|
155
|
+
* target-ignored-SWD signatures — the DAP connect failing ("init mode
|
|
156
|
+
* failed (unable to connect to the target)", i.e. the DPIDR read never
|
|
157
|
+
* succeeded) or a reset/halt never landing ("timed out while waiting for
|
|
158
|
+
* target halted" / "TARGET: <name> - Not halted"). Both mean the board (or
|
|
159
|
+
* probe) needs a power-cycle or the SWD-free DFU path, not a retry of the
|
|
160
|
+
* same command. Centralized so the upload hint stays testable.
|
|
161
|
+
*/
|
|
162
|
+
export declare function isTargetSwdFailure(output: string): boolean;
|
|
114
163
|
/**
|
|
115
164
|
* Cleanse the `west flash` output shown to the user.
|
|
116
165
|
*
|
package/dist/toolchain/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// target is known), GCC errors parsed via the shared parseCompileErrors helper.
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
import { spawnSync } from 'node:child_process';
|
|
21
|
-
import { basename, dirname, join } from 'node:path';
|
|
21
|
+
import { basename, delimiter, dirname, join } from 'node:path';
|
|
22
22
|
import { readdirSync, readFileSync, mkdirSync, rmSync, existsSync, writeFileSync } from 'node:fs';
|
|
23
23
|
import { parseCompileErrors } from '@typecad/cuttlefish/api/shared';
|
|
24
24
|
import { scaffoldZephyrProject, writeIfChanged, appendLibraryOverlayFragments } from './scaffold.js';
|
|
@@ -322,14 +322,31 @@ export function resolveProbeMethod(zc, chip, purpose = 'flash') {
|
|
|
322
322
|
}
|
|
323
323
|
return { ok: true, runner, args: userArgs };
|
|
324
324
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
325
|
+
export function resolveSessionOpenOcd(env = process.env, sdkInstallDir) {
|
|
326
|
+
const exeName = process.platform === 'win32' ? 'openocd.exe' : 'openocd';
|
|
327
|
+
if (env.OPENOCD && existsSync(env.OPENOCD)) {
|
|
328
|
+
return { exe: env.OPENOCD, searchDirs: [] };
|
|
329
|
+
}
|
|
330
|
+
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || sdkInstallDir;
|
|
331
|
+
if (sdkRoot) {
|
|
332
|
+
const exe = join(sdkRoot, 'hosttools', 'openocd', 'bin', exeName);
|
|
333
|
+
if (existsSync(exe)) {
|
|
334
|
+
const searchDirs = [
|
|
335
|
+
join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts'),
|
|
336
|
+
join(sdkRoot, 'hosttools', 'openocd', 'scripts'),
|
|
337
|
+
].filter((d) => existsSync(d));
|
|
338
|
+
return { exe, searchDirs };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
for (const dir of (env.PATH ?? '').split(delimiter)) {
|
|
342
|
+
if (!dir)
|
|
343
|
+
continue;
|
|
344
|
+
const candidate = join(dir, exeName);
|
|
345
|
+
if (existsSync(candidate))
|
|
346
|
+
return { exe: candidate, searchDirs: [] };
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
333
350
|
function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
334
351
|
// Config resolution — two sources, in order:
|
|
335
352
|
// 1. The named probe method's verbatim debugCfg from the board catalog
|
|
@@ -360,17 +377,11 @@ function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
|
360
377
|
.map((a) => a.slice('--cmd-pre-init='.length)),
|
|
361
378
|
];
|
|
362
379
|
const install = discoverWest();
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
return undefined;
|
|
366
|
-
const openocdExe = join(sdkRoot, 'hosttools', 'openocd', 'bin', process.platform === 'win32' ? 'openocd.exe' : 'openocd');
|
|
367
|
-
if (!existsSync(openocdExe))
|
|
380
|
+
const sessionOpenOcd = resolveSessionOpenOcd(process.env, install?.sdkInstallDir);
|
|
381
|
+
if (!sessionOpenOcd)
|
|
368
382
|
return undefined;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const shareScripts = join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts');
|
|
372
|
-
const binScripts = join(sdkRoot, 'hosttools', 'openocd', 'scripts');
|
|
373
|
-
const searchDir = existsSync(shareScripts) ? shareScripts : binScripts;
|
|
383
|
+
const openocdExe = sessionOpenOcd.exe;
|
|
384
|
+
const searchArgs = sessionOpenOcd.searchDirs.flatMap((d) => ['-s', d]);
|
|
374
385
|
let cfgArgs;
|
|
375
386
|
let sessionCfg;
|
|
376
387
|
if (cfgLines && cfgLines.length > 0) {
|
|
@@ -409,7 +420,7 @@ function openocdProbeSession(buildDir, zc, chip, commands) {
|
|
|
409
420
|
cfgArgs = ['-f', sessionCfg];
|
|
410
421
|
}
|
|
411
422
|
const res = spawnSync(openocdExe, [
|
|
412
|
-
|
|
423
|
+
...searchArgs, ...cfgArgs,
|
|
413
424
|
// Pre-init TCL AFTER the cfg (overrides its reset_config) and BEFORE
|
|
414
425
|
// init — the same position west gives --cmd-pre-init.
|
|
415
426
|
...preInit.map((c) => ['-c', c]).flat(),
|
|
@@ -503,6 +514,18 @@ function isUf2DriveVanishRace(output) {
|
|
|
503
514
|
&& /WinError 433/.test(output)
|
|
504
515
|
&& /copymode/.test(output);
|
|
505
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Whether a `west flash` (openocd) output carries one of the known
|
|
519
|
+
* target-ignored-SWD signatures — the DAP connect failing ("init mode
|
|
520
|
+
* failed (unable to connect to the target)", i.e. the DPIDR read never
|
|
521
|
+
* succeeded) or a reset/halt never landing ("timed out while waiting for
|
|
522
|
+
* target halted" / "TARGET: <name> - Not halted"). Both mean the board (or
|
|
523
|
+
* probe) needs a power-cycle or the SWD-free DFU path, not a retry of the
|
|
524
|
+
* same command. Centralized so the upload hint stays testable.
|
|
525
|
+
*/
|
|
526
|
+
export function isTargetSwdFailure(output) {
|
|
527
|
+
return /unable to connect to the target|timed out while waiting for target halted|TARGET: \S+ - Not halted/.test(output);
|
|
528
|
+
}
|
|
506
529
|
/**
|
|
507
530
|
* Cleanse the `west flash` output shown to the user.
|
|
508
531
|
*
|
|
@@ -1112,6 +1135,12 @@ export const Toolchain = {
|
|
|
1112
1135
|
]);
|
|
1113
1136
|
if (revived)
|
|
1114
1137
|
flashNotes.push(revived);
|
|
1138
|
+
// Known SWD-failure signatures get a recovery pointer — a board that
|
|
1139
|
+
// ignores SWD until power-cycled (low-power state, lockup, a wedged
|
|
1140
|
+
// probe) otherwise reads as a toolchain bug.
|
|
1141
|
+
if (isTargetSwdFailure(raw)) {
|
|
1142
|
+
flashNotes.push('-- target ignored SWD — if a retry fails too: power-cycle the board, replug the probe, or skip SWD entirely (hold BOOT0, tap reset, re-run with --probe dfu)');
|
|
1143
|
+
}
|
|
1115
1144
|
}
|
|
1116
1145
|
}
|
|
1117
1146
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecad/framework-zephyr",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.16",
|
|
4
4
|
"description": "TypeCAD framework package for the Zephyr RTOS \u2014 west/CMake build, devicetree-driven GPIO",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"dry-run": "node installer/install.mjs --dry-run"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@typecad/cuttlefish": "1.0.0-alpha.
|
|
42
|
-
"@typecad/hal": "1.0.0-alpha.
|
|
41
|
+
"@typecad/cuttlefish": "1.0.0-alpha.16",
|
|
42
|
+
"@typecad/hal": "1.0.0-alpha.16"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@typecad/expect": "1.0.0-alpha.
|
|
45
|
+
"@typecad/expect": "1.0.0-alpha.16",
|
|
46
46
|
"typescript": "^5.7.3"
|
|
47
47
|
},
|
|
48
48
|
"license": "MIT",
|
|
@@ -369,14 +369,24 @@ function buildLaunchConfig(
|
|
|
369
369
|
// thb (the first user Continue stops at main()) but hands the run/stop
|
|
370
370
|
// transition to cortex-debug.
|
|
371
371
|
//
|
|
372
|
-
// Paths
|
|
373
|
-
//
|
|
374
|
-
|
|
372
|
+
// Paths in GDB commands must be RELATIVE (resolved against GDB's own
|
|
373
|
+
// working directory), never ${workspaceFolder} literals or absolute paths:
|
|
374
|
+
// - cortex-debug passes command strings through verbatim — no variable
|
|
375
|
+
// expansion in postAttachCommands (verified against its gdb.ts) — so a
|
|
376
|
+
// ${workspaceFolder} would reach GDB as a literal.
|
|
377
|
+
// - Even where VS Code expands it, on Windows the expansion carries
|
|
378
|
+
// backslashes that GDB reads as escape sequences (\t → tab, etc.).
|
|
379
|
+
// - An absolute path bakes the generating machine's checkout location
|
|
380
|
+
// into launch.json and breaks on every other clone/checkout.
|
|
381
|
+
// cortex-debug spawns GDB with cwd from the config's `cwd` field
|
|
382
|
+
// (${workspaceFolder} above), so a relative path IS the workspace root —
|
|
383
|
+
// `set directories .` adds exactly the directory the old absolute form did,
|
|
384
|
+
// and `source <rel>` finds the frame-filter script.
|
|
375
385
|
// The Xtensa flash-mapping commands are ESP32-specific (app flash isn't
|
|
376
386
|
// readable until the bootloader maps it); ARM targets drop them.
|
|
377
387
|
const isEsp32Target = o.target.split('/')[0].startsWith('esp32');
|
|
378
388
|
const postAttachCommands = [
|
|
379
|
-
|
|
389
|
+
'set directories .',
|
|
380
390
|
'set remote hardware-watchpoint-limit 2',
|
|
381
391
|
'set remote hardware-breakpoint-limit 2',
|
|
382
392
|
...(isEsp32Target ? [
|
|
@@ -388,7 +398,7 @@ function buildLaunchConfig(
|
|
|
388
398
|
...(isEsp32Target ? ['c'] : []),
|
|
389
399
|
];
|
|
390
400
|
if (gdbScriptRel) {
|
|
391
|
-
postAttachCommands.splice(1, 0, `source ${
|
|
401
|
+
postAttachCommands.splice(1, 0, `source ${gdbScriptRel}`);
|
|
392
402
|
}
|
|
393
403
|
|
|
394
404
|
// Probe-method-driven server shape: jlink-runner methods use cortex-debug's
|
package/src/toolchain/index.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
|
-
import { basename, dirname, join } from 'node:path';
|
|
22
|
+
import { basename, delimiter, dirname, join } from 'node:path';
|
|
23
23
|
import { readdirSync, readFileSync, mkdirSync, rmSync, existsSync, writeFileSync } from 'node:fs';
|
|
24
24
|
import type { ToolchainOptions, CompileResult, UploadResult } from '@typecad/cuttlefish/api/shared';
|
|
25
25
|
import { parseCompileErrors } from '@typecad/cuttlefish/api/shared';
|
|
@@ -402,6 +402,64 @@ export function resolveProbeMethod(
|
|
|
402
402
|
* `commands` are appended after `-f <cfg> -c init`. Returns a flash note on
|
|
403
403
|
* success, undefined when skipped or failed (best-effort by design).
|
|
404
404
|
*/
|
|
405
|
+
/**
|
|
406
|
+
* Resolve the openocd binary (plus script search dirs) for the probe session.
|
|
407
|
+
*
|
|
408
|
+
* Resolution order mirrors how west's own openocd runner finds the binary, so
|
|
409
|
+
* the session and `west flash` drive the SAME openocd:
|
|
410
|
+
* 1. $OPENOCD — the variable Zephyr's CMake reads into the build cache.
|
|
411
|
+
* 2. A Zephyr-SDK-hosted install ($ZEPHYR_SDK_INSTALL_DIR, else the
|
|
412
|
+
* discovered west install's SDK). Layout differs by SDK generation —
|
|
413
|
+
* hosttools/openocd/share/openocd/scripts vs hosttools/openocd/scripts —
|
|
414
|
+
* so both script dirs are collected.
|
|
415
|
+
* 3. PATH — Linux distro / conda / micromamba installs (a micromamba-env
|
|
416
|
+
* openocd is the common Linux setup; the SDK layout check alone made the
|
|
417
|
+
* deterministic probe session unreachable there, silently dropping every
|
|
418
|
+
* Linux flash to the racy `west flash` fallback).
|
|
419
|
+
*
|
|
420
|
+
* A non-SDK binary resolves with no explicit -s dirs: it finds its own
|
|
421
|
+
* interface/target scripts via its compiled-in search path. Returns undefined
|
|
422
|
+
* when no openocd can be found (the caller then uses the west fallback).
|
|
423
|
+
*
|
|
424
|
+
* Exported (pure) so the resolution contract is unit-testable without
|
|
425
|
+
* spawning openocd.
|
|
426
|
+
*/
|
|
427
|
+
export interface SessionOpenOcd {
|
|
428
|
+
readonly exe: string;
|
|
429
|
+
readonly searchDirs: readonly string[];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function resolveSessionOpenOcd(
|
|
433
|
+
env: {
|
|
434
|
+
OPENOCD?: string | undefined;
|
|
435
|
+
ZEPHYR_SDK_INSTALL_DIR?: string | undefined;
|
|
436
|
+
PATH?: string | undefined;
|
|
437
|
+
} = process.env,
|
|
438
|
+
sdkInstallDir?: string,
|
|
439
|
+
): SessionOpenOcd | undefined {
|
|
440
|
+
const exeName = process.platform === 'win32' ? 'openocd.exe' : 'openocd';
|
|
441
|
+
if (env.OPENOCD && existsSync(env.OPENOCD)) {
|
|
442
|
+
return { exe: env.OPENOCD, searchDirs: [] };
|
|
443
|
+
}
|
|
444
|
+
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || sdkInstallDir;
|
|
445
|
+
if (sdkRoot) {
|
|
446
|
+
const exe = join(sdkRoot, 'hosttools', 'openocd', 'bin', exeName);
|
|
447
|
+
if (existsSync(exe)) {
|
|
448
|
+
const searchDirs = [
|
|
449
|
+
join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts'),
|
|
450
|
+
join(sdkRoot, 'hosttools', 'openocd', 'scripts'),
|
|
451
|
+
].filter((d) => existsSync(d));
|
|
452
|
+
return { exe, searchDirs };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
for (const dir of (env.PATH ?? '').split(delimiter)) {
|
|
456
|
+
if (!dir) continue;
|
|
457
|
+
const candidate = join(dir, exeName);
|
|
458
|
+
if (existsSync(candidate)) return { exe: candidate, searchDirs: [] };
|
|
459
|
+
}
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
405
463
|
function openocdProbeSession(
|
|
406
464
|
buildDir: string,
|
|
407
465
|
zc: Record<string, unknown> | undefined,
|
|
@@ -438,16 +496,10 @@ function openocdProbeSession(
|
|
|
438
496
|
];
|
|
439
497
|
|
|
440
498
|
const install = discoverWest();
|
|
441
|
-
const
|
|
442
|
-
if (!
|
|
443
|
-
const openocdExe =
|
|
444
|
-
|
|
445
|
-
if (!existsSync(openocdExe)) return undefined;
|
|
446
|
-
// Script search path: the SDK layouts differ across versions — prefer the
|
|
447
|
-
// share/ form west's own runner uses, fall back to the scripts/ form.
|
|
448
|
-
const shareScripts = join(sdkRoot, 'hosttools', 'openocd', 'share', 'openocd', 'scripts');
|
|
449
|
-
const binScripts = join(sdkRoot, 'hosttools', 'openocd', 'scripts');
|
|
450
|
-
const searchDir = existsSync(shareScripts) ? shareScripts : binScripts;
|
|
499
|
+
const sessionOpenOcd = resolveSessionOpenOcd(process.env, install?.sdkInstallDir);
|
|
500
|
+
if (!sessionOpenOcd) return undefined;
|
|
501
|
+
const openocdExe = sessionOpenOcd.exe;
|
|
502
|
+
const searchArgs = sessionOpenOcd.searchDirs.flatMap((d) => ['-s', d] as [string, string]);
|
|
451
503
|
|
|
452
504
|
let cfgArgs: string[] | undefined;
|
|
453
505
|
let sessionCfg: string | undefined;
|
|
@@ -481,7 +533,7 @@ function openocdProbeSession(
|
|
|
481
533
|
cfgArgs = ['-f', sessionCfg];
|
|
482
534
|
}
|
|
483
535
|
const res = spawnSync(openocdExe, [
|
|
484
|
-
|
|
536
|
+
...searchArgs, ...cfgArgs!,
|
|
485
537
|
// Pre-init TCL AFTER the cfg (overrides its reset_config) and BEFORE
|
|
486
538
|
// init — the same position west gives --cmd-pre-init.
|
|
487
539
|
...preInit.map((c) => ['-c', c] as [string, string]).flat(),
|
|
@@ -587,6 +639,19 @@ function isUf2DriveVanishRace(output: string): boolean {
|
|
|
587
639
|
&& /copymode/.test(output);
|
|
588
640
|
}
|
|
589
641
|
|
|
642
|
+
/**
|
|
643
|
+
* Whether a `west flash` (openocd) output carries one of the known
|
|
644
|
+
* target-ignored-SWD signatures — the DAP connect failing ("init mode
|
|
645
|
+
* failed (unable to connect to the target)", i.e. the DPIDR read never
|
|
646
|
+
* succeeded) or a reset/halt never landing ("timed out while waiting for
|
|
647
|
+
* target halted" / "TARGET: <name> - Not halted"). Both mean the board (or
|
|
648
|
+
* probe) needs a power-cycle or the SWD-free DFU path, not a retry of the
|
|
649
|
+
* same command. Centralized so the upload hint stays testable.
|
|
650
|
+
*/
|
|
651
|
+
export function isTargetSwdFailure(output: string): boolean {
|
|
652
|
+
return /unable to connect to the target|timed out while waiting for target halted|TARGET: \S+ - Not halted/.test(output);
|
|
653
|
+
}
|
|
654
|
+
|
|
590
655
|
/**
|
|
591
656
|
* Cleanse the `west flash` output shown to the user.
|
|
592
657
|
*
|
|
@@ -1216,6 +1281,14 @@ export const Toolchain = {
|
|
|
1216
1281
|
'sleep 300',
|
|
1217
1282
|
]);
|
|
1218
1283
|
if (revived) flashNotes.push(revived);
|
|
1284
|
+
// Known SWD-failure signatures get a recovery pointer — a board that
|
|
1285
|
+
// ignores SWD until power-cycled (low-power state, lockup, a wedged
|
|
1286
|
+
// probe) otherwise reads as a toolchain bug.
|
|
1287
|
+
if (isTargetSwdFailure(raw)) {
|
|
1288
|
+
flashNotes.push(
|
|
1289
|
+
'-- target ignored SWD — if a retry fails too: power-cycle the board, replug the probe, or skip SWD entirely (hold BOOT0, tap reset, re-run with --probe dfu)',
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1219
1292
|
}
|
|
1220
1293
|
} else {
|
|
1221
1294
|
ok = true;
|