@typecad/cuttlefish 1.0.0-alpha.13 → 1.0.0-alpha.15
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/README.md +82 -79
- package/assets/editor-extensions/typecad-debug/README.md +141 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js +347 -0
- package/assets/editor-extensions/typecad-debug/out/extension.js.map +1 -0
- package/assets/editor-extensions/typecad-debug/package.json +76 -0
- package/assets/editor-extensions/typecad-ui/LICENSE +27 -0
- package/assets/editor-extensions/typecad-ui/README.md +52 -0
- package/assets/editor-extensions/typecad-ui/file-icon-dark.png +0 -0
- package/assets/editor-extensions/typecad-ui/file-icon-light.png +0 -0
- package/assets/editor-extensions/typecad-ui/icon.png +0 -0
- package/assets/editor-extensions/typecad-ui/language-configuration.json +43 -0
- package/assets/editor-extensions/typecad-ui/package.json +54 -0
- package/assets/editor-extensions/typecad-ui/snippets/typecad-ui.json +80 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/markdown-ui.json +45 -0
- package/assets/editor-extensions/typecad-ui/syntaxes/typecad-ui.tmLanguage.json +1269 -0
- package/dist/api/config.d.ts +30 -27
- package/dist/api/schema/types.d.ts +20 -3
- package/dist/api/shared/async-runtime-static.d.ts +1 -1
- package/dist/api/shared/async-runtime-static.js +5 -5
- package/dist/api/shared/async-types.d.ts +3 -2
- package/dist/api/shared/board-catalog.d.ts +2 -0
- package/dist/api/shared/board-catalog.js +6 -0
- package/dist/api/shared/board-resolver.d.ts +4 -7
- package/dist/api/shared/board-resolver.js +2 -1
- package/dist/api/shared/coop-scheduler.d.ts +1 -1
- package/dist/api/shared/coop-scheduler.js +1 -1
- package/dist/api/shared/cpp-type-ir.d.ts +1 -1
- package/dist/api/shared/cpp-type-ir.js +1 -1
- package/dist/api/shared/display-profile.d.ts +1 -1
- package/dist/api/shared/display-profile.js +3 -5
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +0 -1
- package/dist/api/shared/framework-manifest.d.ts +65 -507
- package/dist/api/shared/framework-manifest.js +6 -11
- package/dist/api/shared/hal-op-ir.d.ts +294 -685
- package/dist/api/shared/hal-op-ir.js +31 -65
- package/dist/api/shared/index.d.ts +0 -4
- package/dist/api/shared/index.js +1 -7
- package/dist/api/shared/ir-core.d.ts +6 -0
- package/dist/api/shared/ir-declarations.d.ts +4 -0
- package/dist/api/shared/ir.d.ts +1 -3
- package/dist/api/shared/platform-strategy.d.ts +14 -37
- package/dist/api/shared/polyfill-helper-registry.js +0 -5
- package/dist/api/shared/polyfill-types.js +0 -27
- package/dist/api/shared/promise-runtime.js +3 -3
- package/dist/api/shared/spdx-licenses.d.ts +12 -10
- package/dist/api/shared/spdx-licenses.js +1 -1
- package/dist/api/shared/toolchain-types.d.ts +1 -1
- package/dist/api/shared/toolchain-types.js +3 -3
- package/dist/api/shared/types.d.ts +0 -3
- package/dist/api/shared/validate-framework-manifest.js +13 -33
- package/dist/board-catalog/dts-reader.d.ts +220 -0
- package/dist/board-catalog/dts-reader.js +1052 -0
- package/dist/board-catalog/index.d.ts +11 -0
- package/dist/board-catalog/index.js +12 -0
- package/dist/board-catalog/pinconfig.d.ts +48 -0
- package/dist/board-catalog/pinconfig.js +263 -0
- package/dist/board-catalog/sdk.d.ts +82 -0
- package/dist/board-catalog/sdk.js +180 -0
- package/dist/board-catalog/store.d.ts +196 -0
- package/dist/board-catalog/store.js +363 -0
- package/dist/board-catalog/types.d.ts +233 -0
- package/dist/board-catalog/types.js +7 -0
- package/dist/board-catalog/walker.d.ts +112 -0
- package/dist/board-catalog/walker.js +1101 -0
- package/dist/cli-utils.d.ts +2 -2
- package/dist/cli-utils.js +5 -5
- package/dist/cli.js +227 -244
- package/dist/config-loader.d.ts +14 -19
- package/dist/config-loader.js +242 -78
- package/dist/config-schema.d.ts +19 -227
- package/dist/config-schema.js +9 -8
- package/dist/contract/board-generator.d.ts +4 -4
- package/dist/contract/board-generator.js +16 -5
- package/dist/contract/contract-parser.d.ts +36 -127
- package/dist/contract/contract-parser.js +76 -0
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.js +70 -21
- package/dist/create/active-board-catalog.d.ts +9 -0
- package/dist/create/active-board-catalog.js +94 -0
- package/dist/create/board-catalog.generated.d.ts +96 -0
- package/dist/create/board-catalog.generated.js +128737 -0
- package/dist/create/board-search.d.ts +23 -0
- package/dist/create/board-search.js +88 -0
- package/dist/create/editor-integration.d.ts +32 -0
- package/dist/create/editor-integration.js +257 -0
- package/dist/create/eslint-rules-template.d.ts +2 -2
- package/dist/create/framework-catalog.d.ts +48 -8
- package/dist/create/framework-catalog.js +58 -44
- package/dist/create/index.d.ts +8 -13
- package/dist/create/index.js +5 -9
- package/dist/create/mcu-target.d.ts +39 -0
- package/dist/create/mcu-target.js +80 -0
- package/dist/create/pack-targets.d.ts +19 -0
- package/dist/create/pack-targets.js +56 -0
- package/dist/create/{init-scaffold.d.ts → scaffold.d.ts} +8 -11
- package/dist/create/{init-scaffold.js → scaffold.js} +28 -87
- package/dist/create/templates.d.ts +51 -0
- package/dist/create/{init-templates.js → templates.js} +135 -116
- package/dist/create/wizard.d.ts +19 -0
- package/dist/create/wizard.js +314 -0
- package/dist/debug/preprocessor.d.ts +5 -0
- package/dist/debug/preprocessor.js +31 -7
- package/dist/diagnostics/diagnostics-report.d.ts +1 -1
- package/dist/diagnostics/diagnostics-report.js +3 -11
- package/dist/diagnostics/json-schema.d.ts +1 -1
- package/dist/diagnostics/md-writer.js +2 -2
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +1 -1
- package/dist/emit/cpp-emitter.js +4 -1
- package/dist/emit/emitters/class-emitter.js +32 -4
- package/dist/emit/emitters/emitter-context.d.ts +11 -0
- package/dist/emit/emitters/entrypoint-synthesizer.js +1 -1
- package/dist/emit/emitters/function-emitter-impl.js +33 -17
- package/dist/emit/emitters/output-finalizer.d.ts +7 -0
- package/dist/emit/emitters/output-finalizer.js +31 -23
- package/dist/emit/emitters/setup.js +147 -63
- package/dist/emit/emitters/top-level-prep.js +1 -1
- package/dist/emit/emitters/type-decl-emitter.js +13 -8
- package/dist/emit/emitters/ui-emitter.js +1 -1
- package/dist/emit/expression-renderer.d.ts +3 -3
- package/dist/emit/expression-renderer.js +17 -7
- package/dist/emit/route-hal-op.js +17 -48
- package/dist/emit/snprintf-helpers.d.ts +1 -3
- package/dist/emit/snprintf-helpers.js +1 -39
- package/dist/emit/statement-renderer.d.ts +0 -4
- package/dist/emit/statement-renderer.js +54 -45
- package/dist/emit/utils/async-state-machine.d.ts +16 -9
- package/dist/emit/utils/async-state-machine.js +56 -54
- package/dist/emit/utils/comment-helpers.d.ts +0 -8
- package/dist/emit/utils/comment-helpers.js +1 -1
- package/dist/emit/utils/cpp-helpers.d.ts +2 -2
- package/dist/emit/utils/cpp-helpers.js +2 -2
- package/dist/emit/utils/hal-op-cpp-type.js +4 -6
- package/dist/emit/utils/index.d.ts +3 -3
- package/dist/emit/utils/index.js +3 -3
- package/dist/emit/utils/type-inference.d.ts +1 -29
- package/dist/emit/utils/type-inference.js +0 -75
- package/dist/framework-package.d.ts +3 -0
- package/dist/framework-package.js +23 -6
- package/dist/framework-registry.d.ts +5 -5
- package/dist/frameworks/native/graphics/terminal-preview.d.ts +5 -0
- package/dist/frameworks/native/graphics/terminal-preview.js +33 -0
- package/dist/frameworks/native/index.d.ts +3 -0
- package/dist/frameworks/native/index.js +15 -0
- package/dist/frameworks/native/native-compile.d.ts +17 -0
- package/dist/frameworks/native/native-compile.js +170 -0
- package/dist/frameworks/native/native-config.d.ts +41 -0
- package/dist/frameworks/native/native-config.js +7 -0
- package/dist/frameworks/native/strategy.d.ts +97 -0
- package/dist/frameworks/native/strategy.js +664 -0
- package/dist/ir/board-resolver.d.ts +18 -23
- package/dist/ir/board-resolver.js +43 -401
- package/dist/ir/build-ir-state.d.ts +3 -0
- package/dist/ir/build-ir-state.js +42 -0
- package/dist/ir/build-ir.d.ts +1 -1
- package/dist/ir/build-ir.js +64 -45
- package/dist/ir/declaration-builders.js +1 -0
- package/dist/ir/expression-to-ir.js +17 -10
- package/dist/ir/hal/hal-emitter.d.ts +0 -2
- package/dist/ir/hal/hal-emitter.js +92 -41
- package/dist/ir/hal/hal-parser.d.ts +5 -5
- package/dist/ir/hal/hal-parser.js +128 -36
- package/dist/ir/hal/hal-plugins.d.ts +0 -1
- package/dist/ir/hal/hal-plugins.js +425 -716
- package/dist/ir/heap-analysis.js +2 -41
- package/dist/ir/identifier-collector.js +32 -20
- package/dist/ir/network-validation.js +7 -11
- package/dist/ir/ownership-analysis.js +75 -13
- package/dist/ir/peripheral-usage.d.ts +8 -2
- package/dist/ir/peripheral-usage.js +78 -149
- package/dist/ir/peripheral-validation.js +23 -3
- package/dist/ir/pin-capability-validation.d.ts +12 -0
- package/dist/ir/pin-capability-validation.js +61 -97
- package/dist/ir/pin-mode-validation.js +5 -5
- package/dist/ir/program-analysis.d.ts +8 -15
- package/dist/ir/program-analysis.js +140 -323
- package/dist/ir/pulldown-validation.js +11 -4
- package/dist/ir/resource-analysis.js +15 -3
- package/dist/ir/statement-to-ir.js +0 -6
- package/dist/ir/timing-validation.js +1 -1
- package/dist/ir/transformers/array-methods.js +11 -5
- package/dist/ir/transformers/call-statement.js +20 -87
- package/dist/ir/transformers/callback-context-registry.d.ts +1 -1
- package/dist/ir/transformers/callback-context-registry.js +8 -16
- package/dist/ir/transformers/control-flow.js +0 -13
- package/dist/ir/transformers/expressions.js +10 -9
- package/dist/ir/transformers/hal-call-resolver.d.ts +2 -2
- package/dist/ir/transformers/hal-call-resolver.js +24 -10
- package/dist/ir/transformers/hal-emit-helpers.d.ts +2 -2
- package/dist/ir/transformers/hal-emit-helpers.js +2 -2
- package/dist/ir/transformers/namespace-methods.js +1 -1
- package/dist/ir/transformers/ui-callback-lowering.d.ts +0 -2
- package/dist/ir/transformers/ui-callback-lowering.js +0 -24
- package/dist/ir/transformers/variables.js +338 -42
- package/dist/ir/try-catch-validation.js +4 -3
- package/dist/ir/type-resolution.js +15 -4
- package/dist/ir/validation-orchestrator.js +0 -9
- package/dist/libdef/cpp-to-decl.d.ts +0 -9
- package/dist/libdef/cpp-to-decl.js +0 -72
- package/dist/libdef/registry.d.ts +0 -1
- package/dist/libdef/registry.js +1 -24
- package/dist/library/catalog.d.ts +35 -0
- package/dist/library/catalog.js +67 -0
- package/dist/library/cli.d.ts +2 -0
- package/dist/library/cli.js +168 -0
- package/dist/library/init.d.ts +25 -0
- package/dist/library/init.js +397 -0
- package/dist/library/install.d.ts +9 -0
- package/dist/library/install.js +80 -0
- package/dist/library/registry-search.d.ts +34 -0
- package/dist/library/registry-search.js +50 -0
- package/dist/library/validate.d.ts +17 -0
- package/dist/library/validate.js +175 -0
- package/dist/library-packages.d.ts +95 -0
- package/dist/library-packages.js +275 -0
- package/dist/mapping/peripheral-names.js +9 -4
- package/dist/mapping/source-map.d.ts +3 -4
- package/dist/mapping/source-map.js +12 -18
- package/dist/orchestrator/graph-builder.d.ts +3 -3
- package/dist/orchestrator/graph-builder.js +29 -12
- package/dist/orchestrator/type-checker.d.ts +3 -3
- package/dist/orchestrator/type-checker.js +3 -3
- package/dist/platform/async-runtime.js +1 -1
- package/dist/platform/coop-scheduler-runtime.js +2 -2
- package/dist/platform/generic-debug-codegen.d.ts +36 -0
- package/dist/platform/generic-debug-codegen.js +185 -0
- package/dist/platform/generic-strategy.d.ts +3 -4
- package/dist/platform/generic-strategy.js +5 -80
- package/dist/platform/index.js +1 -1
- package/dist/platform/registry.d.ts +2 -2
- package/dist/platform/registry.js +2 -2
- package/dist/preview/client.js +17 -3
- package/dist/safety/asil-decorators.d.ts +32 -0
- package/dist/safety/asil-decorators.js +32 -0
- package/dist/safety/authoring.d.ts +85 -0
- package/dist/safety/authoring.js +61 -0
- package/dist/safety/engine.d.ts +3 -0
- package/dist/safety/engine.js +83 -0
- package/dist/safety/hal/ops.d.ts +48 -0
- package/dist/safety/hal/ops.js +46 -0
- package/dist/safety/iso26262/analyze.d.ts +13 -0
- package/dist/safety/iso26262/analyze.js +29 -0
- package/dist/safety/iso26262/asil.d.ts +10 -0
- package/dist/safety/iso26262/asil.js +22 -0
- package/dist/safety/iso26262/collect.d.ts +14 -0
- package/dist/safety/iso26262/collect.js +137 -0
- package/dist/safety/iso26262/goto-checker.d.ts +10 -0
- package/dist/safety/iso26262/goto-checker.js +72 -0
- package/dist/safety/iso26262/heap-checker.d.ts +7 -0
- package/dist/safety/iso26262/heap-checker.js +57 -0
- package/dist/safety/iso26262/init-checker.d.ts +13 -0
- package/dist/safety/iso26262/init-checker.js +127 -0
- package/dist/safety/iso26262/loop-checker.d.ts +7 -0
- package/dist/safety/iso26262/loop-checker.js +86 -0
- package/dist/safety/iso26262/recursion-checker.d.ts +9 -0
- package/dist/safety/iso26262/recursion-checker.js +134 -0
- package/dist/safety/iso26262/sidecar-writer.d.ts +25 -0
- package/dist/safety/iso26262/sidecar-writer.js +13 -0
- package/dist/safety/passes/pinMode-intercept.d.ts +3 -0
- package/dist/safety/passes/pinMode-intercept.js +60 -0
- package/dist/safety/runtime/mode-table.d.ts +5 -0
- package/dist/safety/runtime/mode-table.js +55 -0
- package/dist/safety/runtime/polyfills.d.ts +6 -0
- package/dist/safety/runtime/polyfills.js +12 -0
- package/dist/safety/runtime/result-struct.d.ts +11 -0
- package/dist/safety/runtime/result-struct.js +98 -0
- package/dist/safety/runtime/safe-int.d.ts +33 -0
- package/dist/safety/runtime/safe-int.js +170 -0
- package/dist/safety/runtime/safe-traits.d.ts +4 -0
- package/dist/safety/runtime/safe-traits.js +95 -0
- package/dist/safety/runtime/safe-variable.d.ts +39 -0
- package/dist/safety/runtime/safe-variable.js +226 -0
- package/dist/safety/runtime/vote.d.ts +8 -0
- package/dist/safety/runtime/vote.js +92 -0
- package/dist/safety/runtime/write-verify.d.ts +8 -0
- package/dist/safety/runtime/write-verify.js +71 -0
- package/dist/safety/safe-int-types.d.ts +45 -0
- package/dist/safety/safe-int-types.js +25 -0
- package/dist/safety/safe-variable-types.d.ts +29 -0
- package/dist/safety/safe-variable-types.js +19 -0
- package/dist/safety/safety-bridge.d.ts +5 -6
- package/dist/safety/safety-bridge.js +15 -28
- package/dist/safety/sidecar-bridge.d.ts +1 -3
- package/dist/safety/sidecar-bridge.js +3 -20
- package/dist/safety/specifiers.d.ts +6 -0
- package/dist/safety/specifiers.js +14 -0
- package/dist/safety-hook.d.ts +8 -7
- package/dist/safety-hook.js +12 -13
- package/dist/testing.d.ts +5 -6
- package/dist/testing.js +5 -5
- package/dist/transpile/resolution.d.ts +1 -1
- package/dist/transpile/resolution.js +18 -9
- package/dist/transpile/test-pins.d.ts +18 -0
- package/dist/transpile/test-pins.js +142 -0
- package/dist/transpile.d.ts +1 -2
- package/dist/transpile.js +90 -29
- package/dist/types.d.ts +45 -26
- package/dist/ui-hook.d.ts +1 -1
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +160 -157
- package/dist/utils/fs.d.ts +3 -4
- package/dist/utils/fs.js +4 -5
- package/dist/utils/strings.d.ts +0 -1
- package/dist/utils/strings.js +0 -8
- package/dist/utils/ui.d.ts +5 -2
- package/dist/utils/ui.js +13 -3
- package/package.json +158 -149
- package/dist/add-preset.d.ts +0 -4
- package/dist/add-preset.js +0 -74
- package/dist/api/shared/async-symbol-detector.d.ts +0 -11
- package/dist/api/shared/async-symbol-detector.js +0 -140
- package/dist/api/shared/native-display-op-resolver.d.ts +0 -10
- package/dist/api/shared/native-display-op-resolver.js +0 -64
- package/dist/create/board-checklist.d.ts +0 -2
- package/dist/create/board-checklist.js +0 -52
- package/dist/create/board-codegen.d.ts +0 -9
- package/dist/create/board-codegen.js +0 -238
- package/dist/create/board-generators.d.ts +0 -12
- package/dist/create/board-generators.js +0 -651
- package/dist/create/board-spec.d.ts +0 -637
- package/dist/create/board-spec.js +0 -214
- package/dist/create/init-templates.d.ts +0 -28
- package/dist/create/init-wizard.d.ts +0 -8
- package/dist/create/init-wizard.js +0 -182
- package/dist/install/framework-catalog.d.ts +0 -53
- package/dist/install/framework-catalog.js +0 -107
- package/dist/install/handle-install.d.ts +0 -35
- package/dist/install/handle-install.js +0 -177
- package/dist/install/index.d.ts +0 -4
- package/dist/install/index.js +0 -3
- package/dist/ir/pin-state-tracking.d.ts +0 -58
- package/dist/ir/pin-state-tracking.js +0 -182
- package/dist/ir/pwm-timer-sharing.d.ts +0 -4
- package/dist/ir/pwm-timer-sharing.js +0 -94
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +0 -4
- package/dist/ir/timer0-pwm-timing-conflict.js +0 -72
- package/dist/libdef/component-decls.d.ts +0 -2
- package/dist/libdef/component-decls.js +0 -6
- package/dist/libdef/component-discovery.d.ts +0 -43
- package/dist/libdef/component-discovery.js +0 -83
- package/dist/theme-tokens.d.ts +0 -22
- package/dist/theme-tokens.js +0 -172
|
@@ -0,0 +1,1101 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// catalog-walker.ts — walk a Zephyr tree's boards/ and build the board data
|
|
3
|
+
// catalog (records + provenance).
|
|
4
|
+
//
|
|
5
|
+
// For every board variant yaml that carries an `identifier:` (the qualified
|
|
6
|
+
// west build target) and a same-basename .dts, extract the board-level facts
|
|
7
|
+
// with the tolerant dts-reader. One record per board VARIANT (identifier),
|
|
8
|
+
// keyed by the identifier.
|
|
9
|
+
//
|
|
10
|
+
// This is the ONE walker shared by the catalog producer:
|
|
11
|
+
// - `cuttlefish board sync` regenerates the machine-local catalog
|
|
12
|
+
// overlay from the user's own Zephyr tree, so board add/change/remove
|
|
13
|
+
// tracks `west update` instead of cuttlefish releases. (There is no
|
|
14
|
+
// compiled-in pack anymore — the overlay is the only catalog.)
|
|
15
|
+
//
|
|
16
|
+
// Silicon facts split by where the SoC's data lives: STM32-style per-pad
|
|
17
|
+
// pinctrl routes are harvested from the vendor HAL dtsi the board's include
|
|
18
|
+
// chain reaches (dts-reader.ts); ESP32's C-header matrices (LEDC channel×pad,
|
|
19
|
+
// SARADC) are shape-parsed from the tree here (harvestEspFamilyHeaders); and
|
|
20
|
+
// families whose silicon map has no in-tree source at all (nRF SAADC AIN
|
|
21
|
+
// pads) are synthesized in the manifest generator's family tables — never
|
|
22
|
+
// board-aware code here.
|
|
23
|
+
// ----------------------------------------------------------------------------
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { readBoardDts } from './dts-reader.js';
|
|
27
|
+
import { harvestPinconfig } from './pinconfig.js';
|
|
28
|
+
/** Cut an inline `# comment` — safe inside quoted values (variant yamls
|
|
29
|
+
* carry them, e.g. ai_m61_32s_kit's revision note). */
|
|
30
|
+
function stripYamlComment(value) {
|
|
31
|
+
const q = value[0];
|
|
32
|
+
if (q === '"' || q === "'") {
|
|
33
|
+
const end = value.indexOf(q, 1);
|
|
34
|
+
return end > 0 ? value.slice(0, end + 1) : value;
|
|
35
|
+
}
|
|
36
|
+
const hash = value.indexOf(' #');
|
|
37
|
+
return hash >= 0 ? value.slice(0, hash) : value;
|
|
38
|
+
}
|
|
39
|
+
/** Minimal line yaml reads (identifier:, name:) — full yaml not needed. */
|
|
40
|
+
function yamlField(file, field) {
|
|
41
|
+
const m = fs.readFileSync(file, 'utf8').match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
42
|
+
return m ? stripYamlComment(m[1]).trim() : undefined;
|
|
43
|
+
}
|
|
44
|
+
/** Read board.yml's board→soc index (see BoardYmlSocIndex). */
|
|
45
|
+
export function boardYmlSocIndex(dir) {
|
|
46
|
+
const yml = path.join(dir, 'board.yml');
|
|
47
|
+
if (!fs.existsSync(yml))
|
|
48
|
+
return { byName: new Map(), uniqueSocs: [], boardNames: [] };
|
|
49
|
+
const byName = new Map();
|
|
50
|
+
const uniqueSocs = [];
|
|
51
|
+
const boardNames = [];
|
|
52
|
+
let inBoardsList = false; // top-level `boards:` present (multi-board format)
|
|
53
|
+
let currentBoard;
|
|
54
|
+
let currentSocs = []; // socs collected for currentBoard
|
|
55
|
+
// A key line with its indent, innermost last. A `- name:` list entry is
|
|
56
|
+
// owned by the topmost key at an indent ≤ the entry's — the tree's
|
|
57
|
+
// board.ymls are not indent-consistent (xiao_ble puts soc entries at the
|
|
58
|
+
// same indent as the socs: key, lyra nests them two deeper), so the
|
|
59
|
+
// enclosing KEY, not the indentation depth, decides ownership.
|
|
60
|
+
const keyStack = [];
|
|
61
|
+
const flushEntry = () => {
|
|
62
|
+
// Only single-soc entries map board name → soc: multi-soc entries need
|
|
63
|
+
// qualified identifiers in their variant yamls.
|
|
64
|
+
if (currentBoard && currentSocs.length === 1)
|
|
65
|
+
byName.set(currentBoard, currentSocs[0]);
|
|
66
|
+
};
|
|
67
|
+
for (const rawLine of fs.readFileSync(yml, 'utf8').split('\n')) {
|
|
68
|
+
const line = rawLine.replace(/\r$/, '');
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
71
|
+
continue;
|
|
72
|
+
const indent = line.length - line.trimStart().length;
|
|
73
|
+
const entry = trimmed.match(/^- name:\s*(.+)$/);
|
|
74
|
+
const key = trimmed.match(/^([\w.-]+):(?:\s|$)/);
|
|
75
|
+
if (key) {
|
|
76
|
+
while (keyStack.length > 0 && keyStack[keyStack.length - 1].indent > indent)
|
|
77
|
+
keyStack.pop();
|
|
78
|
+
keyStack.push({ indent, key: key[1] });
|
|
79
|
+
if (indent === 0 && key[1] === 'boards')
|
|
80
|
+
inBoardsList = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Legacy single-board header: `board: xiao_ble` (value on the key line).
|
|
84
|
+
if (indent === 0) {
|
|
85
|
+
const legacyBoard = trimmed.match(/^board:\s*(\S.*)$/);
|
|
86
|
+
if (legacyBoard)
|
|
87
|
+
boardNames.push(stripYamlComment(legacyBoard[1]).trim());
|
|
88
|
+
}
|
|
89
|
+
if (entry) {
|
|
90
|
+
const name = stripYamlComment(entry[1]).trim();
|
|
91
|
+
if (!name)
|
|
92
|
+
continue;
|
|
93
|
+
// Innermost enclosing key that owns a list at this entry's indent.
|
|
94
|
+
let owner;
|
|
95
|
+
for (let i = keyStack.length - 1; i >= 0; i--) {
|
|
96
|
+
if (keyStack[i].indent <= indent) {
|
|
97
|
+
owner = keyStack[i];
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (owner?.key === 'boards') {
|
|
102
|
+
// A board entry under boards: — its socs follow under its own
|
|
103
|
+
// socs: key.
|
|
104
|
+
flushEntry();
|
|
105
|
+
currentBoard = name;
|
|
106
|
+
currentSocs = [];
|
|
107
|
+
boardNames.push(name);
|
|
108
|
+
}
|
|
109
|
+
else if (owner?.key === 'socs') {
|
|
110
|
+
if (inBoardsList)
|
|
111
|
+
currentSocs.push(name);
|
|
112
|
+
else
|
|
113
|
+
uniqueSocs.push(name);
|
|
114
|
+
}
|
|
115
|
+
// Every other list (variants:, …) is not ours.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
flushEntry();
|
|
119
|
+
return { byName, uniqueSocs, boardNames };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* west runner → probe-method metadata. Ids match the curated soc tables
|
|
123
|
+
* ('stlink', 'dfu', 'jlink', 'bossac', …) so `zephyr.probe` values stay
|
|
124
|
+
* stable across tiers; runners with no curated counterpart use their west
|
|
125
|
+
* runner name as the id (stm32cubeprogrammer, uf2, …). Flash-only tools
|
|
126
|
+
* carry debug: false (a bootloader is not a debugger — the debug path
|
|
127
|
+
* rejects them, resolveProbeMethod enforces it). Simulation-only runners
|
|
128
|
+
* (renode, simics) and debug-server helpers (stlink_gdbserver, trace32)
|
|
129
|
+
* stay unmapped — cuttlefish flashes real hardware.
|
|
130
|
+
*/
|
|
131
|
+
const RUNNER_METHODS = {
|
|
132
|
+
'openocd': { id: 'openocd', description: 'Any SWD/JTAG probe openocd supports', debug: true },
|
|
133
|
+
// Vendor-flavored openocd runner includes — the same openocd runner with
|
|
134
|
+
// vendor defaults (ST: onboard ST-Link; nRF5: nRF tap config). Without
|
|
135
|
+
// these, every ST board (118 in the tree) lost its primary probe method.
|
|
136
|
+
'openocd-stm32': { id: 'openocd', description: 'ST-Link onboard or any SWD probe openocd supports', debug: true },
|
|
137
|
+
'openocd-nrf5': { id: 'openocd', description: 'Any SWD/JTAG probe openocd supports (nRF5 defaults)', debug: true },
|
|
138
|
+
'jlink': { id: 'jlink', description: 'J-Link probe (SWD)', debug: true },
|
|
139
|
+
'dfu-util': { id: 'dfu', description: 'Built-in USB DFU bootloader: hold BOOT0, tap reset (no debug)', debug: false },
|
|
140
|
+
'pyocd': { id: 'pyocd', description: 'Any CMSIS-DAP probe via pyOCD (no debug)', debug: false },
|
|
141
|
+
'blackmagicprobe': { id: 'blackmagicprobe', description: 'Black Magic Probe (no debug)', debug: false },
|
|
142
|
+
'stm32flash': { id: 'stm32flash', description: 'Built-in UART bootloader (no debug)', debug: false },
|
|
143
|
+
'bossac': { id: 'bossac', description: 'Built-in USB bootloader: double-tap reset (no debug)', debug: false },
|
|
144
|
+
'nrfjprog': { id: 'nrfjprog', description: 'Segger nRF command-line flasher (no debug)', debug: false },
|
|
145
|
+
'esptool': { id: 'esptool', description: 'Espressif ROM bootloader over USB-serial (no debug)', debug: false },
|
|
146
|
+
// The ESP32 family's include name for the esptool runner.
|
|
147
|
+
'esp32': { id: 'esptool', description: 'Espressif ROM bootloader over USB-serial (no debug)', debug: false },
|
|
148
|
+
'linkserver': { id: 'linkserver', description: 'NXP LinkServer (no debug)', debug: false },
|
|
149
|
+
'ezflashcli': { id: 'ezflashcli', description: 'Renesas EZ flash CLI (no debug)', debug: false },
|
|
150
|
+
'stm32cubeprogrammer': { id: 'stm32cubeprogrammer', description: 'STM32CubeProgrammer CLI via ST-Link (no debug)', debug: false },
|
|
151
|
+
'nrfutil': { id: 'nrfutil', description: 'nrfutil device — Nordic USB DFU / serial bootloader (no debug)', debug: false },
|
|
152
|
+
'uf2': { id: 'uf2', description: 'UF2 bootloader: copy the firmware file (no debug)', debug: false },
|
|
153
|
+
'silabs_commander': { id: 'silabs_commander', description: 'Simplicity Commander CLI (no debug)', debug: false },
|
|
154
|
+
'probe-rs': { id: 'probe-rs', description: 'probe-rs flasher (no debug)', debug: false },
|
|
155
|
+
'rfp': { id: 'rfp', description: 'Renesas Flash Programmer CLI (no debug)', debug: false },
|
|
156
|
+
'bflb_mcu_tool': { id: 'bflb_mcu_tool', description: 'BouffaloLab bflb_mcu_tool (no debug)', debug: false },
|
|
157
|
+
'xsdb': { id: 'xsdb', description: 'Xilinx xsdb flasher (no debug)', debug: false },
|
|
158
|
+
'wchisp': { id: 'wchisp', description: 'WCH ISP bootloader (no debug)', debug: false },
|
|
159
|
+
'spsdk': { id: 'spsdk', description: 'NXP SPSDK blhost (no debug)', debug: false },
|
|
160
|
+
'minichlink': { id: 'minichlink', description: 'minichlink (WCH CH32, no debug)', debug: false },
|
|
161
|
+
'mdb-hw': { id: 'mdb-hw', description: 'Synopsys MetaWare Debugger, hardware target (no debug)', debug: false },
|
|
162
|
+
'wlink': { id: 'wlink', description: 'WCH-Link probe (no debug)', debug: false },
|
|
163
|
+
'gd32isp': { id: 'gd32isp', description: 'GD32 UART ISP bootloader (no debug)', debug: false },
|
|
164
|
+
'sftool': { id: 'sftool', description: 'SiFli sftool (no debug)', debug: false },
|
|
165
|
+
'teensy': { id: 'teensy', description: 'Teensy loader (no debug)', debug: false },
|
|
166
|
+
'nulink': { id: 'nulink', description: 'Nuvoton NuLink (no debug)', debug: false },
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Scan board.cmake line-by-line tracking if/elseif/else/endif, recording
|
|
170
|
+
* every board_runner_args occurrence and every runner include with its
|
|
171
|
+
* condition stack. Variant selection happens later (pickArgs /
|
|
172
|
+
* activeRunners) — a dir shared by several boards guards each variant's
|
|
173
|
+
* runner args behind its own CONFIG_BOARD_<ID>, and boards guard whole
|
|
174
|
+
* RUNNERS behind core-specific configs (variscite's jlink sits inside
|
|
175
|
+
* if(CONFIG_SOC_MIMX8ML8_M7): the A53 target has no jlink at all).
|
|
176
|
+
* Args containing CMake variable references (${CONFIG_SOC}, …) only
|
|
177
|
+
* resolve inside the build — carried verbatim they would poison a real
|
|
178
|
+
* `west flash` invocation, so they are dropped at capture.
|
|
179
|
+
*/
|
|
180
|
+
function scanBoardCmake(text) {
|
|
181
|
+
const runnerArgs = new Map();
|
|
182
|
+
const includes = [];
|
|
183
|
+
const conds = [];
|
|
184
|
+
for (const line of text.split('\n')) {
|
|
185
|
+
const branch = line.match(/^\s*(if|elseif|else|endif)\b\s*\(?\s*(.*?)\s*\)?\s*$/);
|
|
186
|
+
if (branch) {
|
|
187
|
+
const kw = branch[1];
|
|
188
|
+
const cond = kw === 'else' ? '' : branch[2];
|
|
189
|
+
if (kw === 'if')
|
|
190
|
+
conds.push(cond);
|
|
191
|
+
else if (kw === 'elseif')
|
|
192
|
+
conds[conds.length - 1] = cond;
|
|
193
|
+
else if (kw === 'else')
|
|
194
|
+
conds[conds.length - 1] = '';
|
|
195
|
+
else if (kw === 'endif')
|
|
196
|
+
conds.pop();
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const argsM = line.match(/^\s*board_runner_args\(([\w.-]+)\s+(.*)\)\s*$/);
|
|
200
|
+
if (argsM) {
|
|
201
|
+
const args = [...argsM[2].matchAll(/"([^"]*)"/g)].map((q) => q[1]).filter((q) => !q.includes('${'));
|
|
202
|
+
const list = runnerArgs.get(argsM[1]) ?? [];
|
|
203
|
+
list.push({ conds: [...conds], args });
|
|
204
|
+
runnerArgs.set(argsM[1], list);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const incM = line.match(/^\s*include\(\$\{ZEPHYR_BASE\}\/boards\/common\/([\w.-]+)\.board\.cmake\)/);
|
|
208
|
+
if (incM)
|
|
209
|
+
includes.push({ name: incM[1], conds: [...conds] });
|
|
210
|
+
}
|
|
211
|
+
return { runnerArgs, includes };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Does one condition line apply to THIS target? Empty = always. A
|
|
215
|
+
* CONFIG_BOARD stem must match on a token boundary (a prefix must not
|
|
216
|
+
* match a longer board name); a CONFIG_SOC_<soc> guard matches any
|
|
217
|
+
* variant of that soc, and CONFIG_SOC_<soc>_<X> matches variants whose
|
|
218
|
+
* qualifiers include X.
|
|
219
|
+
*/
|
|
220
|
+
function conditionMatchesTarget(cond, boardConfigs, soc, quals) {
|
|
221
|
+
if (!cond)
|
|
222
|
+
return true;
|
|
223
|
+
const cu = cond.toUpperCase();
|
|
224
|
+
for (const cfg of boardConfigs) {
|
|
225
|
+
if (new RegExp(`CONFIG_BOARD_${cfg}(?![A-Za-z0-9_])`).test(cu))
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if (soc) {
|
|
229
|
+
const socUpper = soc.toUpperCase();
|
|
230
|
+
if (new RegExp(`CONFIG_SOC_${socUpper}(?![A-Za-z0-9_])`).test(cu))
|
|
231
|
+
return true;
|
|
232
|
+
if (quals.some((q) => cu.includes(`_${q.toUpperCase()}`)) && cu.includes(`CONFIG_SOC_${socUpper}`))
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Pick the occurrence for one variant. Priority:
|
|
239
|
+
* 1. a condition naming one of this target's CONFIG_BOARD_<name> tokens
|
|
240
|
+
* (both Zephyr naming shapes: the board.yml entry name — duo_board_b —
|
|
241
|
+
* and the full underscored target — raytac's …_NRF5340_CPUNET), matched
|
|
242
|
+
* with a token boundary so CONFIG_BOARD_IMX8MM_EVK does not match
|
|
243
|
+
* …_EVK_MIMX8MM6_M4;
|
|
244
|
+
* 2. a CONFIG_SOC_<soc>_… condition mentioning one of the target's
|
|
245
|
+
* qualifier tokens (imx8mm guards its A53 args that way);
|
|
246
|
+
* 3. an unconditional occurrence;
|
|
247
|
+
* 4. the first (documented fallback).
|
|
248
|
+
*/
|
|
249
|
+
function pickArgs(list, boardConfigs, soc, quals) {
|
|
250
|
+
for (const cfg of boardConfigs) {
|
|
251
|
+
const boardGuarded = list.find((o) => o.conds.some((c) => conditionMatchesTarget(c, [cfg], undefined, [])));
|
|
252
|
+
if (boardGuarded)
|
|
253
|
+
return boardGuarded.args;
|
|
254
|
+
}
|
|
255
|
+
if (soc && quals.length > 0) {
|
|
256
|
+
const socGuarded = list.find((o) => o.conds.some((c) => c && conditionMatchesTarget(c, [], soc, quals) && /CONFIG_SOC_/i.test(c)));
|
|
257
|
+
if (socGuarded)
|
|
258
|
+
return socGuarded.args;
|
|
259
|
+
}
|
|
260
|
+
const plain = list.find((o) => o.conds.length === 0 || o.conds.every((c) => c === ''));
|
|
261
|
+
if (plain)
|
|
262
|
+
return plain.args;
|
|
263
|
+
return list[0]?.args ?? [];
|
|
264
|
+
}
|
|
265
|
+
export function boardProbeMethods(dir, variant) {
|
|
266
|
+
const cmake = path.join(dir, 'board.cmake');
|
|
267
|
+
if (!fs.existsSync(cmake))
|
|
268
|
+
return undefined;
|
|
269
|
+
const text = fs.readFileSync(cmake, 'utf8');
|
|
270
|
+
const scan = scanBoardCmake(text);
|
|
271
|
+
const boardConfigs = variant?.boardConfigs ?? [];
|
|
272
|
+
const soc = variant?.soc;
|
|
273
|
+
const quals = variant?.quals ?? [];
|
|
274
|
+
// Active runners for THIS variant: unconditional includes always apply;
|
|
275
|
+
// guarded includes apply only when their condition names this target
|
|
276
|
+
// (variscite's jlink is inside if(CONFIG_SOC_…_M7) — the A53 target has
|
|
277
|
+
// no jlink at all, and offering it would flash the wrong core). Only
|
|
278
|
+
// ~24 files in the tree guard every include; a target matching none of
|
|
279
|
+
// a file's guards honestly gets no probe methods.
|
|
280
|
+
let includeNames = scan.includes.map((i) => i.name);
|
|
281
|
+
const anyGuarded = scan.includes.some((i) => i.conds.length > 0 && i.conds.some((c) => c !== ''));
|
|
282
|
+
if (anyGuarded) {
|
|
283
|
+
const active = new Set();
|
|
284
|
+
for (const inc of scan.includes) {
|
|
285
|
+
if (inc.conds.length === 0 || inc.conds.every((c) => c === ''))
|
|
286
|
+
active.add(inc.name);
|
|
287
|
+
else if (inc.conds.some((c) => conditionMatchesTarget(c, boardConfigs, soc, quals)))
|
|
288
|
+
active.add(inc.name);
|
|
289
|
+
}
|
|
290
|
+
includeNames = [...active];
|
|
291
|
+
}
|
|
292
|
+
// First include = west's default runner (preserve scan order).
|
|
293
|
+
const runners = [...new Set(includeNames)];
|
|
294
|
+
if (runners.length === 0)
|
|
295
|
+
return undefined;
|
|
296
|
+
let cfgLines = null;
|
|
297
|
+
const cfgPath = path.join(dir, 'support', 'openocd.cfg');
|
|
298
|
+
if (fs.existsSync(cfgPath)) {
|
|
299
|
+
cfgLines = fs.readFileSync(cfgPath, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith('#'));
|
|
300
|
+
}
|
|
301
|
+
const methods = [];
|
|
302
|
+
for (const runner of runners) {
|
|
303
|
+
// The vendor-flavored openocd includes get the same cfg treatment as
|
|
304
|
+
// plain openocd (stlink interface detection + debugCfg attach).
|
|
305
|
+
if (runner === 'openocd' || runner === 'openocd-stm32' || runner === 'openocd-nrf5') {
|
|
306
|
+
// An ST-Link interface cfg means the method is really 'stlink' — the
|
|
307
|
+
// curated-table id users know. debugCfg carries the whole cfg file,
|
|
308
|
+
// original line order (source/transport/events), comma-free join is
|
|
309
|
+
// not assumed by the consumer.
|
|
310
|
+
const isStlink = cfgLines?.some((l) => l.includes('interface/stlink')) ?? false;
|
|
311
|
+
methods.push({
|
|
312
|
+
id: isStlink ? 'stlink' : 'openocd',
|
|
313
|
+
description: isStlink
|
|
314
|
+
? 'ST-Link or any SWD probe openocd supports (no BOOT0 needed) — also debugs'
|
|
315
|
+
: 'Any SWD/JTAG probe openocd supports — also debugs',
|
|
316
|
+
runner,
|
|
317
|
+
debug: true,
|
|
318
|
+
...(cfgLines ? { debugCfg: cfgLines } : {}),
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const meta = RUNNER_METHODS[runner];
|
|
323
|
+
if (!meta)
|
|
324
|
+
continue;
|
|
325
|
+
// Per-variant args: the occurrence whose CONFIG_BOARD / CONFIG_SOC
|
|
326
|
+
// guard names THIS variant wins (pickArgs).
|
|
327
|
+
const args = pickArgs(scan.runnerArgs.get(runner) ?? [], boardConfigs, soc, quals);
|
|
328
|
+
if (runner === 'jlink') {
|
|
329
|
+
const device = args.find((a) => a.startsWith('--device='));
|
|
330
|
+
methods.push({
|
|
331
|
+
id: 'jlink',
|
|
332
|
+
description: 'J-Link probe (SWD) — also debugs',
|
|
333
|
+
runner,
|
|
334
|
+
debug: true,
|
|
335
|
+
...(device ? { debugDevice: device.slice('--device='.length) } : {}),
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
methods.push({ ...meta, runner, ...(args.length > 0 ? { args: [...args] } : {}) });
|
|
340
|
+
}
|
|
341
|
+
// Dedupe by method id: vendor-flavored includes can coexist with the
|
|
342
|
+
// plain one (openocd + openocd-stm32) — one method per id, first wins.
|
|
343
|
+
const seen = new Set();
|
|
344
|
+
const unique = methods.filter((m) => (seen.has(m.id) ? false : (seen.add(m.id), true)));
|
|
345
|
+
return unique.length > 0 ? unique : undefined;
|
|
346
|
+
}
|
|
347
|
+
/** VERSION file → 'MAJOR.MINOR.PATCH' ('' when unreadable). */
|
|
348
|
+
export function zephyrVersionOf(zephyrBase) {
|
|
349
|
+
try {
|
|
350
|
+
const text = fs.readFileSync(path.join(zephyrBase, 'VERSION'), 'utf8');
|
|
351
|
+
const m = text.match(/VERSION_MAJOR\s*=\s*(\d+)[\s\S]*?VERSION_MINOR\s*=\s*(\d+)/);
|
|
352
|
+
if (!m)
|
|
353
|
+
return '';
|
|
354
|
+
const patch = text.match(/PATCHLEVEL\s*=\s*(\d+)/);
|
|
355
|
+
return patch ? `${m[1]}.${m[2]}.${patch[1]}` : `${m[1]}.${m[2]}`;
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return '';
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Git HEAD of the tree, read straight off the filesystem (no git spawn —
|
|
363
|
+
* sync runs where git may be absent). Handles the plain clone (.git dir,
|
|
364
|
+
* HEAD → ref → loose ref or packed-refs) and the worktree/submodule form
|
|
365
|
+
* (.git file pointing at a gitdir). Best-effort: undefined when unreadable.
|
|
366
|
+
*/
|
|
367
|
+
export function gitHeadOf(zephyrBase) {
|
|
368
|
+
try {
|
|
369
|
+
const dotGit = path.join(zephyrBase, '.git');
|
|
370
|
+
let gitDir = dotGit;
|
|
371
|
+
const st = fs.statSync(dotGit);
|
|
372
|
+
if (st.isFile()) {
|
|
373
|
+
const text = fs.readFileSync(dotGit, 'utf8');
|
|
374
|
+
const m = text.match(/gitdir:\s*(.+)/);
|
|
375
|
+
if (!m)
|
|
376
|
+
return undefined;
|
|
377
|
+
const dir = m[1].trim();
|
|
378
|
+
gitDir = path.isAbsolute(dir) ? dir : path.resolve(zephyrBase, dir);
|
|
379
|
+
}
|
|
380
|
+
const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
|
|
381
|
+
if (!head.startsWith('ref: '))
|
|
382
|
+
return head || undefined;
|
|
383
|
+
const ref = head.slice(5).trim();
|
|
384
|
+
const refFile = path.join(gitDir, ref);
|
|
385
|
+
if (fs.existsSync(refFile))
|
|
386
|
+
return fs.readFileSync(refFile, 'utf8').trim() || undefined;
|
|
387
|
+
const packed = fs.readFileSync(path.join(gitDir, 'packed-refs'), 'utf8');
|
|
388
|
+
const m = packed.match(new RegExp(`^([0-9a-f]{40,}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'));
|
|
389
|
+
return m ? m[1] : undefined;
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Parse board.yml into its declared build targets, both shapes:
|
|
397
|
+
* multi-board (`boards:` list) and legacy (`board:` block), with socs and
|
|
398
|
+
* (nested) variants. Variant chains accumulate through nesting levels
|
|
399
|
+
* (board/soc/v1/v2).
|
|
400
|
+
*/
|
|
401
|
+
export function boardYmlTargets(dir) {
|
|
402
|
+
const yml = path.join(dir, 'board.yml');
|
|
403
|
+
if (!fs.existsSync(yml))
|
|
404
|
+
return [];
|
|
405
|
+
const targets = [];
|
|
406
|
+
const boards = [];
|
|
407
|
+
let board;
|
|
408
|
+
let soc;
|
|
409
|
+
const keyStack = [];
|
|
410
|
+
const chainBases = [];
|
|
411
|
+
let chain = [];
|
|
412
|
+
const flushSoc = () => {
|
|
413
|
+
if (board && soc) {
|
|
414
|
+
board.socs.set(soc.name, soc.chains);
|
|
415
|
+
soc = undefined;
|
|
416
|
+
chain = [];
|
|
417
|
+
chainBases.length = 0;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const flushBoard = () => {
|
|
421
|
+
flushSoc();
|
|
422
|
+
board = undefined;
|
|
423
|
+
};
|
|
424
|
+
for (const rawLine of fs.readFileSync(yml, 'utf8').split('\n')) {
|
|
425
|
+
const line = rawLine.replace(/\r$/, '');
|
|
426
|
+
const trimmed = line.trim();
|
|
427
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
428
|
+
continue;
|
|
429
|
+
const indent = line.length - line.trimStart().length;
|
|
430
|
+
const entry = trimmed.match(/^- name:\s*(.+)$/);
|
|
431
|
+
const key = trimmed.match(/^([\w.-]+):(?:\s+(.*))?$/);
|
|
432
|
+
if (key && !trimmed.startsWith('-')) {
|
|
433
|
+
while (keyStack.length > 0 && keyStack[keyStack.length - 1].indent > indent)
|
|
434
|
+
keyStack.pop();
|
|
435
|
+
const k = key[1];
|
|
436
|
+
const v = (key[2] ?? '').trim();
|
|
437
|
+
if (indent === 0 && k === 'boards') {
|
|
438
|
+
flushBoard();
|
|
439
|
+
}
|
|
440
|
+
else if (indent === 0 && k === 'board' && v) {
|
|
441
|
+
// Legacy inline form: board: xiao_ble
|
|
442
|
+
flushBoard();
|
|
443
|
+
board = { name: stripYamlComment(v).trim(), socs: new Map() };
|
|
444
|
+
boards.push(board);
|
|
445
|
+
}
|
|
446
|
+
else if (k === 'variants') {
|
|
447
|
+
// Chains under this level accumulate from the enclosing chain.
|
|
448
|
+
chainBases.push([...chain]);
|
|
449
|
+
}
|
|
450
|
+
else if ((k === 'name' || k === 'full_name' || k === 'vendor') && v) {
|
|
451
|
+
const val = stripYamlComment(v).trim();
|
|
452
|
+
// A name: under a legacy board: block names the board.
|
|
453
|
+
if (k === 'name' && !board && keyStack.some((s) => s.key === 'board')) {
|
|
454
|
+
board = { name: val, socs: new Map() };
|
|
455
|
+
boards.push(board);
|
|
456
|
+
}
|
|
457
|
+
else if (k !== 'name' && board && !soc) {
|
|
458
|
+
if (k === 'full_name')
|
|
459
|
+
board.fullName = val;
|
|
460
|
+
if (k === 'vendor')
|
|
461
|
+
board.vendor = val;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
keyStack.push({ indent, key: k });
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (entry) {
|
|
468
|
+
const name = stripYamlComment(entry[1]).trim().replace(/^['"]|['"]$/g, '');
|
|
469
|
+
if (!name)
|
|
470
|
+
continue;
|
|
471
|
+
let owner;
|
|
472
|
+
for (let i = keyStack.length - 1; i >= 0; i--) {
|
|
473
|
+
if (keyStack[i].indent <= indent) {
|
|
474
|
+
owner = keyStack[i].key;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (owner === 'boards') {
|
|
479
|
+
flushBoard();
|
|
480
|
+
board = { name, socs: new Map() };
|
|
481
|
+
boards.push(board);
|
|
482
|
+
}
|
|
483
|
+
else if (owner === 'socs') {
|
|
484
|
+
flushSoc();
|
|
485
|
+
soc = { name, chains: [] };
|
|
486
|
+
}
|
|
487
|
+
else if (owner === 'variants') {
|
|
488
|
+
const base = chainBases[chainBases.length - 1] ?? [];
|
|
489
|
+
chain = [...base, name];
|
|
490
|
+
soc?.chains.push([...chain]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
flushBoard();
|
|
495
|
+
for (const b of boards) {
|
|
496
|
+
for (const [socName, chains] of b.socs) {
|
|
497
|
+
targets.push({ identifier: `${b.name}/${socName}`, boardName: b.name, soc: socName, chain: [], ...(b.fullName ? { fullName: b.fullName } : {}), ...(b.vendor ? { vendor: b.vendor } : {}) });
|
|
498
|
+
for (const c of chains) {
|
|
499
|
+
targets.push({ identifier: [b.name, socName, ...c].join('/'), boardName: b.name, soc: socName, chain: c, ...(b.fullName ? { fullName: b.fullName } : {}), ...(b.vendor ? { vendor: b.vendor } : {}) });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return targets;
|
|
504
|
+
}
|
|
505
|
+
/** Compose the catalog record from one DTS's extracted facts. The canonical
|
|
506
|
+
* LED/button picks follow the DEVICETREE numbering, not child order: led0
|
|
507
|
+
* when aliased (mm_swiftio declares led0 = green_led as the SECOND child —
|
|
508
|
+
* picking the first aliased child made the manifest's LED0 contradict the
|
|
509
|
+
* devicetree's led0). Same for sw0. Extras then carry the remaining
|
|
510
|
+
* aliased nodes and the LED<N>/BUTTON<N> indices line up with dtSpec
|
|
511
|
+
* numbering. */
|
|
512
|
+
// ── ESP32 family header harvest ────────────────────────────────────────────
|
|
513
|
+
// ESP32 SoCs publish their silicon matrices as C headers, not pinctrl dtsi:
|
|
514
|
+
// the LEDC channel×pad macros in <zephyr>/include/zephyr/dt-bindings/pinctrl/
|
|
515
|
+
// <soc>-pinctrl.h (LEDC_CH<ch>_GPIO<pin> — every pair) and the SARADC map in
|
|
516
|
+
// the HAL module's components/soc/<soc>/include/soc/adc_channel.h
|
|
517
|
+
// (ADC<unit>_GPIO<pad>_CHANNEL <ch>). Both are shape-parsed; the file paths
|
|
518
|
+
// key on the target's soc segment (a data convention, like the letter-port
|
|
519
|
+
// families), never on board names.
|
|
520
|
+
function harvestEspFamilyHeaders(identifier, facts, zephyrBase, westRoot) {
|
|
521
|
+
const soc = identifier.split('/')[1];
|
|
522
|
+
if (!soc || !/^(esp32|esp32c)/.test(soc))
|
|
523
|
+
return {};
|
|
524
|
+
const out = {};
|
|
525
|
+
try {
|
|
526
|
+
const pinH = path.join(zephyrBase, 'include', 'zephyr', 'dt-bindings', 'pinctrl', soc + '-pinctrl.h');
|
|
527
|
+
if (fs.existsSync(pinH)) {
|
|
528
|
+
const src = fs.readFileSync(pinH, 'utf8');
|
|
529
|
+
const pads = new Set();
|
|
530
|
+
let maxCh = -1;
|
|
531
|
+
for (const m of src.matchAll(/#define LEDC_CH(\d+)_GPIO(\d+)\b/g)) {
|
|
532
|
+
pads.add(Number(m[2]));
|
|
533
|
+
maxCh = Math.max(maxCh, Number(m[1]));
|
|
534
|
+
}
|
|
535
|
+
if (pads.size > 0 && maxCh >= 0) {
|
|
536
|
+
out.pwmMatrix = { controller: facts.ledcNode ?? 'ledc0', channelCount: maxCh + 1, pads: [...pads].sort((a, b) => a - b) };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch { /* header absent — no matrix facts */ }
|
|
541
|
+
try {
|
|
542
|
+
const adcH = path.join(westRoot, 'modules', 'hal', 'espressif', 'components', 'soc', soc, 'include', 'soc', 'adc_channel.h');
|
|
543
|
+
if (fs.existsSync(adcH)) {
|
|
544
|
+
const src = fs.readFileSync(adcH, 'utf8');
|
|
545
|
+
const routes = [];
|
|
546
|
+
for (const m of src.matchAll(/#define ADC(\d+)_GPIO(\d+)_CHANNEL\s+(\d+)/g)) {
|
|
547
|
+
// DT nodelabels: adc0 carries unit 1, adc1 unit 2.
|
|
548
|
+
routes.push({ source: 'adc' + (Number(m[1]) - 1), pad: Number(m[2]), channel: Number(m[3]) });
|
|
549
|
+
}
|
|
550
|
+
if (routes.length > 0)
|
|
551
|
+
out.espAdc = routes;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch { /* header absent — no adc facts */ }
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
// ── RP2 header matrices ─────────────────────────────────────────────────────
|
|
558
|
+
// The RP2040/RP2350 publish their silicon routing as pinmux macros in
|
|
559
|
+
// include/zephyr/dt-bindings/pinctrl/rpi-pico-*-pinctrl.h: the ADC map
|
|
560
|
+
// (ADC_CH<n>_P<pad>, in the SoC headers — channels 0-3 = P26-P29 on BOTH
|
|
561
|
+
// SoCs) and the PWM slice map (PWM_<slice><A|B>_P<pad>, in the shared
|
|
562
|
+
// common header — every GPIO is PWM-capable, slice = pad/2, A/B = pad%2).
|
|
563
|
+
// The macro token IS the overlay's pinmux payload: the RP2 ADC and PWM
|
|
564
|
+
// drivers both apply pinctrl, so routes carry it for group synthesis.
|
|
565
|
+
function rp2PinctrlHeaderFile(soc) {
|
|
566
|
+
if (soc === 'rp2040')
|
|
567
|
+
return ['rpi-pico-rp2040-pinctrl.h', 'rpi-pico-pinctrl-common.h'];
|
|
568
|
+
if (soc === 'rp2350a')
|
|
569
|
+
return ['rpi-pico-rp2350a-pinctrl.h', 'rpi-pico-pinctrl-common.h'];
|
|
570
|
+
if (soc === 'rp2350b')
|
|
571
|
+
return ['rpi-pico-rp2350b-pinctrl.h', 'rpi-pico-pinctrl-common.h'];
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
function harvestRp2PinctrlHeaders(identifier, zephyrBase) {
|
|
575
|
+
const soc = identifier.split('/')[1];
|
|
576
|
+
const files = soc ? rp2PinctrlHeaderFile(soc) : undefined;
|
|
577
|
+
if (!files)
|
|
578
|
+
return {};
|
|
579
|
+
const adc = new Map();
|
|
580
|
+
const pwm = new Map();
|
|
581
|
+
for (const f of files) {
|
|
582
|
+
let src;
|
|
583
|
+
try {
|
|
584
|
+
src = fs.readFileSync(path.join(zephyrBase, 'include', 'zephyr', 'dt-bindings', 'pinctrl', f), 'utf8');
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
for (const m of src.matchAll(/#define (ADC_CH(\d+)_P(\d+))\b/g)) {
|
|
590
|
+
adc.set(m[1], { source: 'adc', channel: Number(m[2]), pad: Number(m[3]), pinctrl: m[1] });
|
|
591
|
+
}
|
|
592
|
+
for (const m of src.matchAll(/#define (PWM_(\d+)([AB])_P(\d+))\b/g)) {
|
|
593
|
+
// The pwm-rp2 driver decodes its channel cell as slice*2 + (B?1:0).
|
|
594
|
+
pwm.set(m[1], { source: 'pwm', channel: Number(m[2]) * 2 + (m[3] === 'B' ? 1 : 0), pad: Number(m[4]), pinctrl: m[1] });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
...(adc.size > 0 ? { padAdc: [...adc.values()].sort((a, b) => a.channel - b.channel) } : {}),
|
|
599
|
+
...(pwm.size > 0 ? { padPwm: [...pwm.values()].sort((a, b) => a.pad - b.pad) } : {}),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
// ── Vendor pinconfig YAMLs (GD32 / Atmel / Bouffalolab) ─────────────────────
|
|
603
|
+
// The datasheet-sourced pin tables under each HAL module's `pinconfigs/`
|
|
604
|
+
// directory — see board-catalog/pinconfig.ts. ADC + (Atmel) DAC routes for
|
|
605
|
+
// now; the walker's coverage ledger records the families whose PWM pinmux
|
|
606
|
+
// synthesis is still pending instead of shipping false capability flags.
|
|
607
|
+
function composeRecord(base, facts, zephyrBase, westRoot) {
|
|
608
|
+
const esp = harvestEspFamilyHeaders(base.identifier, facts, zephyrBase, westRoot);
|
|
609
|
+
const rp2 = harvestRp2PinctrlHeaders(base.identifier, zephyrBase);
|
|
610
|
+
const pinconfig = harvestPinconfig(base.identifier, westRoot);
|
|
611
|
+
const adcRoutes = [...facts.adcPins, ...(pinconfig?.adc ?? [])];
|
|
612
|
+
// SAM DAC needs no pinmux group (the driver selects the output pad), so the
|
|
613
|
+
// pinconfig DAC route carries no pinctrl token — an empty token keeps it out
|
|
614
|
+
// of the overlay's pinctrl-0 emission, exactly like the ESP32 DAC synthesis.
|
|
615
|
+
const dacRoutes = [...facts.dacPins, ...(pinconfig?.dac ?? []).map((d) => ({ ...d, pinctrl: '' }))];
|
|
616
|
+
// SAM PWM routes carry the synthesized WO pinmux macro token (pinctrl), so
|
|
617
|
+
// they join the pwmPins pipeline verbatim — the overlay synthesizes the
|
|
618
|
+
// pad group from the macro like any other macro-form route.
|
|
619
|
+
const pwmRoutes = [...facts.pwmPins, ...(pinconfig?.pwm ?? [])];
|
|
620
|
+
const led = facts.leds.find((l) => l.alias === 'led0') ?? facts.leds.find((l) => l.alias);
|
|
621
|
+
const button = facts.buttons.find((b) => b.alias === 'sw0') ?? facts.buttons.find((b) => b.alias);
|
|
622
|
+
const rec = {
|
|
623
|
+
...base,
|
|
624
|
+
console: facts.chosen['zephyr,console'],
|
|
625
|
+
// Addressable user LED (ws2812) only when no gpio-leds LED exists —
|
|
626
|
+
// a board with both keeps its devicetree-chosen led0.
|
|
627
|
+
...(facts.stripLed && !led ? { stripLed: { controller: facts.stripLed.controller, pin: facts.stripLed.pin } } : {}),
|
|
628
|
+
...(facts.usbDevice ? { usbDevice: facts.usbDevice } : {}),
|
|
629
|
+
...(facts.usbController ? { usbController: facts.usbController } : {}),
|
|
630
|
+
// Board-level watchdog0 alias wins; boards that never wrote one fall
|
|
631
|
+
// back to the SoC-level watchdog node (nearly every SoC ships IWDG —
|
|
632
|
+
// the alias is authorial habit, not a hardware difference).
|
|
633
|
+
...(facts.aliases['watchdog0'] || facts.wdtSocNode
|
|
634
|
+
? { wdtNodeLabel: facts.aliases['watchdog0'] ?? facts.wdtSocNode }
|
|
635
|
+
: {}),
|
|
636
|
+
...(facts.buses.i2c.length > 0 || facts.buses.spi.length > 0 || facts.buses.uart.length > 0
|
|
637
|
+
? { buses: facts.buses }
|
|
638
|
+
: {}),
|
|
639
|
+
...(facts.pwmLeds.length > 0
|
|
640
|
+
? { pwmLeds: facts.pwmLeds.map((p) => ({
|
|
641
|
+
...(p.alias ? { alias: p.alias } : {}),
|
|
642
|
+
controller: p.controller, channel: p.channel,
|
|
643
|
+
...(p.periodNs != null ? { periodNs: p.periodNs } : {}),
|
|
644
|
+
...(p.flags && p.flags.length > 0 ? { flags: [...p.flags] } : {}),
|
|
645
|
+
})) }
|
|
646
|
+
: {}),
|
|
647
|
+
// Silicon PWM/analog routes harvested from the SoC pinctrl files in the
|
|
648
|
+
// board's include chain (raw port/bit form — the manifest generator maps
|
|
649
|
+
// to global pin numbers and applies nodelabel conventions).
|
|
650
|
+
...(pwmRoutes.length > 0 ? { pwmPins: pwmRoutes } : {}),
|
|
651
|
+
...(adcRoutes.length > 0 ? { adcPins: adcRoutes } : {}),
|
|
652
|
+
...(dacRoutes.length > 0 ? { dacPins: dacRoutes } : {}),
|
|
653
|
+
...(facts.analogDevices.length > 0 ? { analogDevices: facts.analogDevices } : {}),
|
|
654
|
+
...(esp.pwmMatrix ? { pwmMatrix: esp.pwmMatrix } : {}),
|
|
655
|
+
...(facts.pwmNodes && facts.pwmNodes.length > 0 ? { pwmNodes: facts.pwmNodes } : {}),
|
|
656
|
+
...(rp2.padAdc ? { padAdc: rp2.padAdc } : {}),
|
|
657
|
+
...(rp2.padPwm ? { padPwm: rp2.padPwm } : {}),
|
|
658
|
+
...(facts.connectorAdc.length > 0 ? { connectorAdc: facts.connectorAdc } : {}),
|
|
659
|
+
...(facts.pinctrlWarnings && facts.pinctrlWarnings.length > 0 ? { pinctrlWarnings: facts.pinctrlWarnings } : {}),
|
|
660
|
+
...(facts.flashKb ? { flashKb: facts.flashKb } : {}),
|
|
661
|
+
...(facts.hasStoragePartition ? { hasStoragePartition: true } : {}),
|
|
662
|
+
...(facts.storageReg ? { storageReg: facts.storageReg } : {}),
|
|
663
|
+
...(facts.counterNodes.length > 0 ? { counterNodes: facts.counterNodes } : {}),
|
|
664
|
+
...(facts.gpioControllers.length > 0 ? { gpioControllers: facts.gpioControllers } : {}),
|
|
665
|
+
...(esp.espAdc ? { espAdc: esp.espAdc } : {}),
|
|
666
|
+
...(led ? { led: { dtSpec: led.alias, controller: led.controller, pin: led.pin, flags: led.flags } } : {}),
|
|
667
|
+
...(button ? { button: { dtSpec: button.alias, controller: button.controller, pin: button.pin, flags: button.flags } } : {}),
|
|
668
|
+
...(facts.leds.length > 1 || facts.buttons.length > 1
|
|
669
|
+
? {
|
|
670
|
+
extraLeds: facts.leds.filter((l) => l !== led && l.alias).map((l) => ({ dtSpec: l.alias, controller: l.controller, pin: l.pin, flags: l.flags })),
|
|
671
|
+
extraButtons: facts.buttons.filter((b) => b !== button && b.alias).map((b) => ({ dtSpec: b.alias, controller: b.controller, pin: b.pin, flags: b.flags })),
|
|
672
|
+
}
|
|
673
|
+
: {}),
|
|
674
|
+
...(facts.connectors.filter((c) => Object.keys(c.pins).length > 0).length > 0
|
|
675
|
+
? { connectors: facts.connectors.filter((c) => Object.keys(c.pins).length > 0).map((c) => ({ nodelabel: c.nodelabel, compatible: c.compatible, pins: c.pins })) }
|
|
676
|
+
: {}),
|
|
677
|
+
};
|
|
678
|
+
// prune empty arrays
|
|
679
|
+
for (const k of ['extraLeds', 'extraButtons'])
|
|
680
|
+
if (rec[k]?.length === 0)
|
|
681
|
+
delete rec[k];
|
|
682
|
+
// Coverage ledger: which source satisfied each capability (see
|
|
683
|
+
// BoardDataEntry.siliconSources). nRF SAADC's pad map is synthesized in
|
|
684
|
+
// boardgen (family table) — the walker records pwmNodes (the nRF psel
|
|
685
|
+
// matrix) as 'family'; SAADC itself is only boardgen-visible.
|
|
686
|
+
const siliconSources = {};
|
|
687
|
+
if (pinconfig?.adc.length)
|
|
688
|
+
siliconSources.adc = 'pinconfig';
|
|
689
|
+
else if (facts.adcPins.length)
|
|
690
|
+
siliconSources.adc = 'pinctrl';
|
|
691
|
+
else if (esp.espAdc?.length)
|
|
692
|
+
siliconSources.adc = 'header';
|
|
693
|
+
else if (rp2.padAdc?.length)
|
|
694
|
+
siliconSources.adc = 'header';
|
|
695
|
+
else if (facts.connectorAdc.length)
|
|
696
|
+
siliconSources.adc = 'connector';
|
|
697
|
+
if (facts.pwmPins.length)
|
|
698
|
+
siliconSources.pwm = 'pinctrl';
|
|
699
|
+
else if (pinconfig?.pwm?.length)
|
|
700
|
+
siliconSources.pwm = 'pinconfig';
|
|
701
|
+
else if (esp.pwmMatrix)
|
|
702
|
+
siliconSources.pwm = 'header';
|
|
703
|
+
else if (rp2.padPwm?.length)
|
|
704
|
+
siliconSources.pwm = 'header';
|
|
705
|
+
else if (facts.pwmNodes?.length)
|
|
706
|
+
siliconSources.pwm = 'family';
|
|
707
|
+
if (facts.dacPins.length)
|
|
708
|
+
siliconSources.dac = 'pinctrl';
|
|
709
|
+
else if (pinconfig?.dac?.length)
|
|
710
|
+
siliconSources.dac = 'pinconfig';
|
|
711
|
+
if (Object.keys(siliconSources).length > 0)
|
|
712
|
+
rec.siliconSources = siliconSources;
|
|
713
|
+
const hasFacts = Boolean(rec.console || rec.led || rec.button || rec.connectors);
|
|
714
|
+
return { record: rec, hasFacts };
|
|
715
|
+
}
|
|
716
|
+
/** Probe-method pin for one identifier (CONFIG_BOARD stems + soc quals). */
|
|
717
|
+
function variantPinFor(identifier, socIndex) {
|
|
718
|
+
const parts = identifier.split('/');
|
|
719
|
+
const underscored = identifier.replace(/\//g, '_');
|
|
720
|
+
const entry = socIndex.boardNames.find((n) => identifier === n || identifier.startsWith(n + '/'));
|
|
721
|
+
return {
|
|
722
|
+
boardConfigs: [...new Set([...(entry ? [entry] : []), underscored])]
|
|
723
|
+
.map((n) => n.replace(/[^A-Za-z0-9]/g, '_').toUpperCase()),
|
|
724
|
+
...(parts[1] ? { soc: parts[1] } : {}),
|
|
725
|
+
...(parts.length > 2 ? { quals: parts.slice(2) } : {}),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/** Candidate .dts files for one identifier, best match first: the yaml's own
|
|
729
|
+
* basename, then progressively de-qualified shared forms — an @revision
|
|
730
|
+
* qualifier is stripped (nrf9160dk@0.7.0/nrf9160 → nrf9160dk/nrf9160) and
|
|
731
|
+
* trailing segments are dropped one at a time (…/nrf9160/ns shares the base
|
|
732
|
+
* nrf9160dk_nrf9160.dts). Boards that ship ONLY revision-qualified yamls
|
|
733
|
+
* (nrf9160dk) reach their base dts exclusively through these fallbacks. */
|
|
734
|
+
function dtsCandidatesFor(dir, identifier, yamlBase) {
|
|
735
|
+
const out = [];
|
|
736
|
+
if (yamlBase)
|
|
737
|
+
out.push(path.join(dir, yamlBase + '.dts'));
|
|
738
|
+
const segments = identifier.split('/');
|
|
739
|
+
segments[0] = segments[0].split('@')[0];
|
|
740
|
+
for (let n = segments.length; n >= 1; n--) {
|
|
741
|
+
const c = path.join(dir, segments.slice(0, n).join('_') + '.dts');
|
|
742
|
+
if (!out.includes(c))
|
|
743
|
+
out.push(c);
|
|
744
|
+
}
|
|
745
|
+
return out;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Board-dir overlays (user-facing `<board>*.overlay` files) document the
|
|
749
|
+
* analog header wiring the base DTS leaves out — Renesas RA boards carry
|
|
750
|
+
* BOTH the connector gpio-map and the io-channel-map ONLY there (joined by
|
|
751
|
+
* the shared child-spec token, e.g. ARDUINO_HEADER_R3_A0). Same trust level
|
|
752
|
+
* as the DK dtsi maps: board-authored wiring. Two joins: the overlay's own
|
|
753
|
+
* gpio-map (token → pad), then the base-DTS connectors' labels (comment
|
|
754
|
+
* token first, Arduino `A<idx>` position convention for numeric specs).
|
|
755
|
+
*/
|
|
756
|
+
function overlayConnectorAdc(dir, files, connectors) {
|
|
757
|
+
const out = [];
|
|
758
|
+
for (const f of files) {
|
|
759
|
+
if (!f.endsWith('.overlay'))
|
|
760
|
+
continue;
|
|
761
|
+
let text;
|
|
762
|
+
try {
|
|
763
|
+
text = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const map = text.match(/io-channel-map\s*=([^;]*);/s);
|
|
769
|
+
if (!map)
|
|
770
|
+
continue;
|
|
771
|
+
// The overlay's own gpio-map: <child-spec 0 &ctrl PIN flags> → pad.
|
|
772
|
+
// Child specs are tokens (macros like ARDUINO_HEADER_R3_A0 or indexes).
|
|
773
|
+
const overlayPads = new Map();
|
|
774
|
+
const gpiomap = text.match(/gpio-map\s*=([^;]*);/s);
|
|
775
|
+
if (gpiomap) {
|
|
776
|
+
for (const g of gpiomap[1].matchAll(/<([\w]+)\s+\d+\s+&([\w,-]+)\s+(\d+)\s+\d+>/g)) {
|
|
777
|
+
if (!overlayPads.has(g[1]))
|
|
778
|
+
overlayPads.set(g[1], { controller: g[2], pin: Number(g[3]) });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
for (const m of map[1].matchAll(/<([\w]+)\s+&([\w,-]+)\s+(\d+)\s*>[\s]*(?:\/\*\s*([A-Za-z_][\w-]*)?[^*]*\*\/)?/g)) {
|
|
782
|
+
const channel = Number(m[3]);
|
|
783
|
+
if (!Number.isFinite(channel))
|
|
784
|
+
continue;
|
|
785
|
+
const token = m[1];
|
|
786
|
+
// Join 1: the same token in the overlay's gpio-map.
|
|
787
|
+
const pad = overlayPads.get(token);
|
|
788
|
+
if (pad) {
|
|
789
|
+
out.push({ source: m[2], channel, controller: pad.controller, pin: pad.pin });
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
// Join 2: base-DTS connector labels — the comment's first token, else
|
|
793
|
+
// the Arduino uno-adc position convention (numeric index N = A<N>).
|
|
794
|
+
const idx = Number(token);
|
|
795
|
+
const labels = [
|
|
796
|
+
...(m[4] ? [m[4]] : []),
|
|
797
|
+
...(Number.isInteger(idx) && idx >= 0 && idx <= 7 ? [`A${idx}`] : []),
|
|
798
|
+
];
|
|
799
|
+
for (const c of connectors) {
|
|
800
|
+
for (const label of labels) {
|
|
801
|
+
const ref = c.pins[label];
|
|
802
|
+
if (ref) {
|
|
803
|
+
out.push({ source: m[2], channel, controller: ref.controller, pin: ref.pin });
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return out;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Walk `<zephyrBase>/boards` and extract one record per board variant.
|
|
814
|
+
* Reader failures degrade to the base record (identifier/name/vendor/dts +
|
|
815
|
+
* probe methods) — a board whose DTS defeats the reader still resolves, it
|
|
816
|
+
* just carries no pin facts.
|
|
817
|
+
*/
|
|
818
|
+
// Extraction revision history (GENERATOR_REV lives in store.ts):
|
|
819
|
+
// 1 initial; 2 multi-board board.yml + first-wins runner args + comma
|
|
820
|
+
// labels; 3 vendor runner aliases; 4 comment-before-property; 5 duplicate
|
|
821
|
+
// runner dedupe; 6 full flasher coverage + macro-arg filter; 7 per-variant
|
|
822
|
+
// guard selection; 8 board.yml-synthesized targets; 9 bus/usb/wdt facts +
|
|
823
|
+
// the move into cuttlefish (the compiled-in pack is gone); 10 silicon
|
|
824
|
+
// pinctrl harvest (STM32 tim/adc routes via module dts roots); 11 dac
|
|
825
|
+
// routes + SoC-level watchdog fallback + zephyr arch dts roots; 12
|
|
826
|
+
// pinconfigs YAML ADC + gpio-controller inventory + siliconSources ledger.
|
|
827
|
+
export function walkBoardCatalog(zephyrBase) {
|
|
828
|
+
const boardsRoot = path.join(zephyrBase, 'boards');
|
|
829
|
+
if (!fs.existsSync(boardsRoot)) {
|
|
830
|
+
throw new Error(`no boards/ directory at ${boardsRoot} — not a Zephyr tree?`);
|
|
831
|
+
}
|
|
832
|
+
// Vendor HAL module dts roots (Zephyr's MODULE_DTS_ROOTS, mirrored): the
|
|
833
|
+
// west workspace keeps modules/ beside the zephyr checkout, and the STM32
|
|
834
|
+
// pinctrl dtsi the board DTS includes (<st/f4/…-pinctrl.dtsi>) resolves
|
|
835
|
+
// from modules/hal/stm32/dts. Discovered, not hardcoded — a tree without
|
|
836
|
+
// modules simply yields no silicon pinctrl facts.
|
|
837
|
+
const westRoot = path.dirname(zephyrBase);
|
|
838
|
+
const moduleDtsRoots = [];
|
|
839
|
+
const discoverDtsRoots = (parent) => {
|
|
840
|
+
try {
|
|
841
|
+
for (const d of fs.readdirSync(parent, { withFileTypes: true })) {
|
|
842
|
+
if (!d.isDirectory())
|
|
843
|
+
continue;
|
|
844
|
+
const candidate = path.join(parent, d.name, 'dts');
|
|
845
|
+
if (fs.existsSync(candidate))
|
|
846
|
+
moduleDtsRoots.push(candidate);
|
|
847
|
+
// Zephyr module dts roots also expose `include/` for angle includes
|
|
848
|
+
// (`<dt-bindings/pinctrl/…>` — GigaDevice ships its per-part pinctrl
|
|
849
|
+
// headers there, not under dts/).
|
|
850
|
+
const includeRoot = path.join(parent, d.name, 'include');
|
|
851
|
+
if (fs.existsSync(path.join(includeRoot, 'dt-bindings')))
|
|
852
|
+
moduleDtsRoots.push(includeRoot);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
// no such parent — plain zephyr checkout without a west workspace
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
discoverDtsRoots(path.join(westRoot, 'modules'));
|
|
860
|
+
discoverDtsRoots(path.join(westRoot, 'modules', 'hal'));
|
|
861
|
+
// The zephyr tree's own arch dts roots (dts/arm, dts/xtensa, …): board
|
|
862
|
+
// DTS files include their SoC dtsi through them (`<st/f4/…Xe.dtsi>`),
|
|
863
|
+
// which is where SoC-level nodes (the STM32 iwdg watchdog) live.
|
|
864
|
+
try {
|
|
865
|
+
const dtsRoot = path.join(zephyrBase, 'dts');
|
|
866
|
+
for (const d of fs.readdirSync(dtsRoot, { withFileTypes: true })) {
|
|
867
|
+
if (d.isDirectory())
|
|
868
|
+
moduleDtsRoots.push(path.join(dtsRoot, d.name));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch {
|
|
872
|
+
// no dts/ dir — not a full checkout
|
|
873
|
+
}
|
|
874
|
+
const records = [];
|
|
875
|
+
let variants = 0, withFacts = 0, failures = 0, droppedYamls = 0;
|
|
876
|
+
const vendors = fs.readdirSync(boardsRoot, { withFileTypes: true })
|
|
877
|
+
.filter((d) => d.isDirectory());
|
|
878
|
+
for (const vendorDir of vendors) {
|
|
879
|
+
const boardDirs = fs.readdirSync(path.join(boardsRoot, vendorDir.name), { withFileTypes: true })
|
|
880
|
+
.filter((d) => d.isDirectory());
|
|
881
|
+
for (const boardDir of boardDirs) {
|
|
882
|
+
const dir = path.join(boardsRoot, vendorDir.name, boardDir.name);
|
|
883
|
+
const files = fs.readdirSync(dir);
|
|
884
|
+
const socIndex = boardYmlSocIndex(dir);
|
|
885
|
+
const seenIds = new Set();
|
|
886
|
+
for (const f of files) {
|
|
887
|
+
if (!f.endsWith('.yaml') || f === 'board.yml')
|
|
888
|
+
continue;
|
|
889
|
+
const raw = yamlField(path.join(dir, f), 'identifier');
|
|
890
|
+
if (!raw)
|
|
891
|
+
continue;
|
|
892
|
+
// Qualify: bare identifiers take the soc segment from board.yml —
|
|
893
|
+
// their own entry's soc in multi-board dirs, the sole soc in
|
|
894
|
+
// single-board dirs.
|
|
895
|
+
const identifier = raw.includes('/')
|
|
896
|
+
? raw
|
|
897
|
+
: (() => {
|
|
898
|
+
const soc = socIndex.byName.get(raw)
|
|
899
|
+
?? (socIndex.uniqueSocs.length === 1 ? socIndex.uniqueSocs[0] : null);
|
|
900
|
+
return soc ? `${raw}/${soc}` : null;
|
|
901
|
+
})();
|
|
902
|
+
if (!identifier || seenIds.has(identifier))
|
|
903
|
+
continue;
|
|
904
|
+
// Resolve the dts BEFORE claiming the identifier: a yaml whose
|
|
905
|
+
// same-basename dts is missing falls back to the board's shared base
|
|
906
|
+
// dts (revision-qualified yamls — nrf9160dk ships only
|
|
907
|
+
// *_0_14_0.yaml over nrf9160dk_nrf9160.dts). Marking seenIds first
|
|
908
|
+
// made such yamls vanish AND blocked the board.yml synthesis pass
|
|
909
|
+
// from covering the identifier — whole boards (nrf9160dk, the
|
|
910
|
+
// mt81xx ADSP targets) dropped out of the catalog silently.
|
|
911
|
+
const dtsFile = dtsCandidatesFor(dir, identifier, f.replace(/\.yaml$/, ''))
|
|
912
|
+
.find((p) => fs.existsSync(p));
|
|
913
|
+
if (!dtsFile) {
|
|
914
|
+
droppedYamls++;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
variants++;
|
|
918
|
+
seenIds.add(identifier);
|
|
919
|
+
// Per-variant: runner args are selected by the variant's own
|
|
920
|
+
// CONFIG_BOARD guard, so this must run per yaml, not per dir.
|
|
921
|
+
const probeMethods = boardProbeMethods(dir, variantPinFor(identifier, socIndex));
|
|
922
|
+
const base = {
|
|
923
|
+
identifier,
|
|
924
|
+
name: yamlField(path.join(dir, f), 'name') ?? identifier,
|
|
925
|
+
vendor: vendorDir.name,
|
|
926
|
+
dts: path.basename(dtsFile),
|
|
927
|
+
...(probeMethods ? { probeMethods } : {}),
|
|
928
|
+
};
|
|
929
|
+
try {
|
|
930
|
+
let facts = readBoardDts(dtsFile, { zephyrBoardsRoot: boardsRoot, moduleDtsRoots });
|
|
931
|
+
{
|
|
932
|
+
// Overlay wiring appended, deduped by (source, channel, pad) —
|
|
933
|
+
// sibling variant overlays (uno_r4 minima/wifi) repeat entries.
|
|
934
|
+
const seen = new Set(facts.connectorAdc.map((r) => `${r.source}:${r.channel}:${r.controller}.${r.pin}`));
|
|
935
|
+
const extra = overlayConnectorAdc(dir, files, facts.connectors)
|
|
936
|
+
.filter((r) => !seen.has(`${r.source}:${r.channel}:${r.controller}.${r.pin}`));
|
|
937
|
+
facts = { ...facts, connectorAdc: [...facts.connectorAdc, ...extra] };
|
|
938
|
+
}
|
|
939
|
+
const { record, hasFacts } = composeRecord(base, facts, zephyrBase, westRoot);
|
|
940
|
+
if (hasFacts)
|
|
941
|
+
withFacts++;
|
|
942
|
+
records.push(record);
|
|
943
|
+
}
|
|
944
|
+
catch (err) {
|
|
945
|
+
failures++;
|
|
946
|
+
if (failures <= 5)
|
|
947
|
+
console.error(`reader failed on ${identifier}: ${err.message}`);
|
|
948
|
+
records.push(base);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// Synthesis pass: board.yml-driven dirs ship NO per-variant yamls —
|
|
952
|
+
// nucleo_n657x0_q's `sb` target exists only as a board.yml entry.
|
|
953
|
+
// Emit every declared target the yaml flow did not already cover.
|
|
954
|
+
for (const t of boardYmlTargets(dir)) {
|
|
955
|
+
if (seenIds.has(t.identifier))
|
|
956
|
+
continue;
|
|
957
|
+
// No yamlBase preference here — the MOST qualified candidate must
|
|
958
|
+
// win (Zephyr's own variant dts chain: board_soc_variant → board_soc
|
|
959
|
+
// → board); the chain's tail covers the boardName fallback.
|
|
960
|
+
const dtsFile = dtsCandidatesFor(dir, t.identifier)
|
|
961
|
+
.find((p) => fs.existsSync(p));
|
|
962
|
+
if (!dtsFile)
|
|
963
|
+
continue;
|
|
964
|
+
seenIds.add(t.identifier);
|
|
965
|
+
variants++;
|
|
966
|
+
const probeMethods = boardProbeMethods(dir, variantPinFor(t.identifier, socIndex));
|
|
967
|
+
const base = {
|
|
968
|
+
identifier: t.identifier,
|
|
969
|
+
name: t.fullName ?? t.identifier,
|
|
970
|
+
vendor: t.vendor ?? vendorDir.name,
|
|
971
|
+
dts: path.basename(dtsFile),
|
|
972
|
+
...(probeMethods ? { probeMethods } : {}),
|
|
973
|
+
};
|
|
974
|
+
try {
|
|
975
|
+
let facts = readBoardDts(dtsFile, { zephyrBoardsRoot: boardsRoot, moduleDtsRoots });
|
|
976
|
+
{
|
|
977
|
+
// Overlay wiring appended, deduped by (source, channel, pad) —
|
|
978
|
+
// sibling variant overlays (uno_r4 minima/wifi) repeat entries.
|
|
979
|
+
const seen = new Set(facts.connectorAdc.map((r) => `${r.source}:${r.channel}:${r.controller}.${r.pin}`));
|
|
980
|
+
const extra = overlayConnectorAdc(dir, files, facts.connectors)
|
|
981
|
+
.filter((r) => !seen.has(`${r.source}:${r.channel}:${r.controller}.${r.pin}`));
|
|
982
|
+
facts = { ...facts, connectorAdc: [...facts.connectorAdc, ...extra] };
|
|
983
|
+
}
|
|
984
|
+
const { record, hasFacts } = composeRecord(base, facts, zephyrBase, westRoot);
|
|
985
|
+
if (hasFacts)
|
|
986
|
+
withFacts++;
|
|
987
|
+
records.push(record);
|
|
988
|
+
}
|
|
989
|
+
catch (err) {
|
|
990
|
+
failures++;
|
|
991
|
+
if (failures <= 5)
|
|
992
|
+
console.error(`reader failed on ${t.identifier}: ${err.message}`);
|
|
993
|
+
records.push(base);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const coverage = {
|
|
999
|
+
adc: {},
|
|
1000
|
+
pwm: {},
|
|
1001
|
+
dac: {},
|
|
1002
|
+
};
|
|
1003
|
+
for (const r of records) {
|
|
1004
|
+
for (const cap of ['adc', 'pwm', 'dac']) {
|
|
1005
|
+
const src = r.siliconSources?.[cap];
|
|
1006
|
+
if (src)
|
|
1007
|
+
coverage[cap][src] = (coverage[cap][src] ?? 0) + 1;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
boards: Object.fromEntries(records.map((r) => [r.identifier, r])),
|
|
1012
|
+
provenance: {
|
|
1013
|
+
zephyrBase: path.resolve(zephyrBase),
|
|
1014
|
+
version: zephyrVersionOf(zephyrBase),
|
|
1015
|
+
...(gitHeadOf(zephyrBase) ? { gitHead: gitHeadOf(zephyrBase) } : {}),
|
|
1016
|
+
boardsMtimeMs: fs.statSync(boardsRoot).mtimeMs,
|
|
1017
|
+
generatedAt: new Date().toISOString(),
|
|
1018
|
+
variants: records.length,
|
|
1019
|
+
},
|
|
1020
|
+
stats: { variants, withFacts, failures, droppedYamls, coverage },
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
// ---------------------------------------------------------------------------
|
|
1024
|
+
// SoC-level facts from the installed tree — contract/custom-board support.
|
|
1025
|
+
// A custom PCB has no Zephyr board record, but its SoC's dtsi lives in the
|
|
1026
|
+
// installed tree and declares the soc's bus controllers (i2c0, usart1, ...).
|
|
1027
|
+
// ---------------------------------------------------------------------------
|
|
1028
|
+
/** Bus controller labels declared by the SoC's dtsi files in the installed
|
|
1029
|
+
* tree, classified by family. Best-effort: matches dts/dtsi files whose
|
|
1030
|
+
* name contains the soc name (a soc stem like 'stm32f411' matches
|
|
1031
|
+
* 'stm32f411xe.dtsi'), plus same-dir includes. */
|
|
1032
|
+
export function socBusLabelsFromTree(zephyrBase, soc) {
|
|
1033
|
+
const buses = { i2c: [], spi: [], uart: [] };
|
|
1034
|
+
const dtsRoot = path.join(zephyrBase, 'dts');
|
|
1035
|
+
if (!fs.existsSync(dtsRoot))
|
|
1036
|
+
return buses;
|
|
1037
|
+
const socLower = soc.toLowerCase();
|
|
1038
|
+
// Family-prefix stems: a soc's buses often live in a SHARED family dtsi
|
|
1039
|
+
// (stm32f411xe → stm32f4.dtsi declares usart1/i2c1), so match progressively
|
|
1040
|
+
// shorter prefixes of the soc name, down to 5 chars.
|
|
1041
|
+
const stems = new Set();
|
|
1042
|
+
for (let len = socLower.length; len >= 5; len--)
|
|
1043
|
+
stems.add(socLower.slice(0, len));
|
|
1044
|
+
const labeled = { i2c: [], spi: [], uart: [] };
|
|
1045
|
+
let order = 0;
|
|
1046
|
+
const seenFiles = new Set();
|
|
1047
|
+
const scan = (dir, depth) => {
|
|
1048
|
+
if (depth > 4)
|
|
1049
|
+
return;
|
|
1050
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1051
|
+
const full = path.join(dir, entry.name);
|
|
1052
|
+
if (entry.isDirectory()) {
|
|
1053
|
+
scan(full, depth + 1);
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (!/\.dtsi?$/.test(entry.name))
|
|
1057
|
+
continue;
|
|
1058
|
+
const nameLower = entry.name.toLowerCase();
|
|
1059
|
+
const matches = [...stems].some((s) => nameLower.includes(s));
|
|
1060
|
+
if (!matches || seenFiles.has(full))
|
|
1061
|
+
continue;
|
|
1062
|
+
seenFiles.add(full);
|
|
1063
|
+
const weight = nameLower.length;
|
|
1064
|
+
let text;
|
|
1065
|
+
try {
|
|
1066
|
+
text = fs.readFileSync(full, 'utf8');
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
const grab = (re, family) => {
|
|
1072
|
+
for (const m of text.matchAll(re)) {
|
|
1073
|
+
if (!m[1])
|
|
1074
|
+
continue;
|
|
1075
|
+
labeled[family].push({ label: m[1], weight, order: order++ });
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
grab(/([a-z0-9_]+):\s*(?:i2c|twi)@/g, 'i2c');
|
|
1079
|
+
grab(/([a-z0-9_]+):\s*(?:spi|ssp)@/g, 'spi');
|
|
1080
|
+
// 'serial@' — STM32 names its usart nodes serial@<addr>.
|
|
1081
|
+
grab(/([a-z0-9_]+):\s*(?:uart|usart|eusart|serial)@/g, 'uart');
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
scan(dtsRoot, 0);
|
|
1085
|
+
// Drop flash-controller aliases (octospi/xspi/quadspi/subghzspi are
|
|
1086
|
+
// XIP/flash buses, not user SPI) and the usb-serial console pseudo-node.
|
|
1087
|
+
const notFlash = (label) => !/^(octospi|xspi|quadspi|subghzspi)/.test(label);
|
|
1088
|
+
const notUsbSerial = (label) => label !== 'usb_serial';
|
|
1089
|
+
const finalize = (family) => {
|
|
1090
|
+
const seen = new Set();
|
|
1091
|
+
return labeled[family]
|
|
1092
|
+
.filter((e) => notFlash(e.label) && notUsbSerial(e.label))
|
|
1093
|
+
.sort((a, b) => (a.weight - b.weight) || (a.order - b.order))
|
|
1094
|
+
.filter((e) => (seen.has(e.label) ? false : (seen.add(e.label), true)))
|
|
1095
|
+
.map((e) => e.label);
|
|
1096
|
+
};
|
|
1097
|
+
buses.i2c = finalize('i2c');
|
|
1098
|
+
buses.spi = finalize('spi');
|
|
1099
|
+
buses.uart = finalize('uart');
|
|
1100
|
+
return buses;
|
|
1101
|
+
}
|