@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,1052 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// dts-reader.ts — tolerant Zephyr board-devicetree reader
|
|
3
|
+
//
|
|
4
|
+
// Extracts the BOARD-LEVEL facts the generated board module needs from a
|
|
5
|
+
// board's own DTS files: aliases, the chosen console, gpio-leds / gpio-keys
|
|
6
|
+
// children (LED/BUTTON devicetree specs), connector nexus gpio-maps
|
|
7
|
+
// (arduino-header-r3, seeed,xiao-gpio), buses, USB wiring, watchdog,
|
|
8
|
+
// flash/storage and counters. Silicon pinctrl routes (STM32 tim/adc/dac
|
|
9
|
+
// *-pinctrl.dtsi) are harvested here in RAW form — the manifest generator
|
|
10
|
+
// maps them to global pins and carries the family conventions for SoCs whose
|
|
11
|
+
// silicon data is not in devicetree (ESP32 C headers, nRF SAADC AIN tables).
|
|
12
|
+
//
|
|
13
|
+
// DELIBERATELY NOT a devicetree compiler. The reader follows only the
|
|
14
|
+
// board's LOCAL include chain (relative *.dtsi and in-tree <...> includes it
|
|
15
|
+
// can resolve), and treats C macros it cannot expand as opaque flag tokens
|
|
16
|
+
// (`GPIO_ACTIVE_LOW` stays the literal text). It does not resolve phandles
|
|
17
|
+
// beyond &label capture, does not evaluate expressions, and ignores every
|
|
18
|
+
// property it has no consumer for. Boards whose interesting nodes live in
|
|
19
|
+
// deeper SoC includes simply yield fewer facts — the generator's tiering
|
|
20
|
+
// handles that honestly.
|
|
21
|
+
//
|
|
22
|
+
// Used by the catalog walker: `cuttlefish board sync` at user-sync time
|
|
23
|
+
// against the installed Zephyr tree (and by the fixture-tree walker tests).
|
|
24
|
+
// ----------------------------------------------------------------------------
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
function resolveInclude(spec, fromFile, roots) {
|
|
28
|
+
// Strip the wrapper: <...> or "..."
|
|
29
|
+
const inner = spec.slice(1, -1);
|
|
30
|
+
// Quoted includes are relative to the including file.
|
|
31
|
+
if (!spec.startsWith('<')) {
|
|
32
|
+
const p = path.resolve(path.dirname(fromFile), inner);
|
|
33
|
+
return fs.existsSync(p) ? p : undefined;
|
|
34
|
+
}
|
|
35
|
+
// Board files use ../boards/common/... relative includes and bare filenames
|
|
36
|
+
// resolved against the boards tree; try the file's own dir first, then the
|
|
37
|
+
// configured angle roots.
|
|
38
|
+
const local = path.resolve(path.dirname(fromFile), inner);
|
|
39
|
+
if (fs.existsSync(local))
|
|
40
|
+
return local;
|
|
41
|
+
for (const root of roots.angle) {
|
|
42
|
+
const p = path.resolve(root, inner);
|
|
43
|
+
if (fs.existsSync(p))
|
|
44
|
+
return p;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Strip block/line comments, keeping one attachment channel for gpio-map labels. */
|
|
49
|
+
function stripCommentsKeepMarkers(src) {
|
|
50
|
+
// Replace comment bodies but KEEP a marker: /* D0 */ → ⟨D0⟩ inline, so the
|
|
51
|
+
// gpio-map parser can pick silkscreen labels out of the cell stream.
|
|
52
|
+
return src
|
|
53
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => '⟨' + m.slice(2, -2).trim() + '⟩')
|
|
54
|
+
.replace(/\/\/[^\n]*/g, '');
|
|
55
|
+
}
|
|
56
|
+
function emptyNode(name, parent) {
|
|
57
|
+
return { labels: [], name, props: new Map(), children: new Map(), parent };
|
|
58
|
+
}
|
|
59
|
+
/** Statement scanner: emits node headers (at every '{'), property
|
|
60
|
+
* statements ('a = <...>' at their owning depth), and node closes ('}')
|
|
61
|
+
* in source order, building the node tree with &label override merges.
|
|
62
|
+
* `refOverrides` records every label an `&label { }` block targeted —
|
|
63
|
+
* with SoC dtsis absorbed, an override merges into the real labeled node
|
|
64
|
+
* instead of creating a synthetic '&label' one, so this set is the only
|
|
65
|
+
* way to tell "the board overrode this node" from "the SoC defines it". */
|
|
66
|
+
function parseStatements(src) {
|
|
67
|
+
const root = emptyNode('/');
|
|
68
|
+
const byLabel = new Map();
|
|
69
|
+
const refOverrides = new Set();
|
|
70
|
+
const stack = [root];
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let start = 0;
|
|
73
|
+
const emit = (kind, text) => {
|
|
74
|
+
if (kind === 'end') {
|
|
75
|
+
if (stack.length > 1)
|
|
76
|
+
stack.pop();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const t = text.trim().replace(/^[\r\n\t ]+/, '');
|
|
80
|
+
if (!t)
|
|
81
|
+
return;
|
|
82
|
+
const top = stack[stack.length - 1];
|
|
83
|
+
if (kind === 'hdr') {
|
|
84
|
+
const refHeader = t.match(/^&([\w-]+)\s*\{?$/);
|
|
85
|
+
if (refHeader) {
|
|
86
|
+
refOverrides.add(refHeader[1]);
|
|
87
|
+
let n = byLabel.get(refHeader[1]);
|
|
88
|
+
if (!n) {
|
|
89
|
+
n = emptyNode('&' + refHeader[1], top);
|
|
90
|
+
byLabel.set(refHeader[1], n);
|
|
91
|
+
// Attach to the synthetic tree so subtree walkers (connectors,
|
|
92
|
+
// gpio nodes, led strips, bus pinctrl) see &ref blocks — the SoC
|
|
93
|
+
// dtsi that parents them is not part of the local include chain.
|
|
94
|
+
top.children.set(n.name, n);
|
|
95
|
+
}
|
|
96
|
+
stack.push(n);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Header text may carry trailing debris from prior statements (the
|
|
100
|
+
// scanner slices from after the previous ';'/'}'), so isolate the LAST
|
|
101
|
+
// node-header line: [label:] name[@unit]
|
|
102
|
+
const headerLine = t.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean).pop() ?? t;
|
|
103
|
+
const parts = headerLine.split(':').map((s) => s.trim()).filter(Boolean);
|
|
104
|
+
const name = parts.length > 0 ? parts[parts.length - 1] : 'anon';
|
|
105
|
+
const labels = parts.slice(0, -1);
|
|
106
|
+
let node = top.children.get(name);
|
|
107
|
+
if (!node) {
|
|
108
|
+
node = emptyNode(name, top);
|
|
109
|
+
top.children.set(name, node);
|
|
110
|
+
}
|
|
111
|
+
for (const l of labels) {
|
|
112
|
+
if (!node.labels.includes(l))
|
|
113
|
+
node.labels.push(l);
|
|
114
|
+
byLabel.set(l, node);
|
|
115
|
+
}
|
|
116
|
+
stack.push(node);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// property
|
|
120
|
+
// Leading comment markers (a comment line above a property, inside the
|
|
121
|
+
// same node — 96b_nitrogen's `/* gpio flags need validation */` before
|
|
122
|
+
// button0's gpios) must not hide the property: the anchored match would
|
|
123
|
+
// fail and the node would silently lose its facts. Strip them first;
|
|
124
|
+
// markers INSIDE a value stay (gpio-map silkscreen labels).
|
|
125
|
+
const stripped = t.replace(/^(?:⟨[^⟩]*⟩\s*)+/, '');
|
|
126
|
+
const prop = stripped.match(/^([\w,#@-]+)\s*=\s*([\s\S]*?)\s*;?$/);
|
|
127
|
+
if (prop && top !== root) {
|
|
128
|
+
top.props.set(prop[1], prop[2].replace(/;$/, ''));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const bare = stripped.match(/^([\w,#@-]+)\s*;?$/);
|
|
132
|
+
if (bare && top !== root)
|
|
133
|
+
top.props.set(bare[1], '');
|
|
134
|
+
};
|
|
135
|
+
let i = 0;
|
|
136
|
+
while (i < src.length) {
|
|
137
|
+
const ch = src[i];
|
|
138
|
+
if (ch === '{') {
|
|
139
|
+
const stmt = src.slice(start, i);
|
|
140
|
+
emit('hdr', stmt);
|
|
141
|
+
depth++;
|
|
142
|
+
start = i + 1;
|
|
143
|
+
}
|
|
144
|
+
else if (ch === '}') {
|
|
145
|
+
const inner = src.slice(start, i).trim();
|
|
146
|
+
if (inner)
|
|
147
|
+
emit('prop', inner);
|
|
148
|
+
emit('end', '');
|
|
149
|
+
if (depth > 0)
|
|
150
|
+
depth--;
|
|
151
|
+
start = i + 1;
|
|
152
|
+
}
|
|
153
|
+
else if (ch === ';') {
|
|
154
|
+
const stmt = src.slice(start, i);
|
|
155
|
+
if (depth > 0)
|
|
156
|
+
emit('prop', stmt);
|
|
157
|
+
start = i + 1;
|
|
158
|
+
}
|
|
159
|
+
i++;
|
|
160
|
+
}
|
|
161
|
+
return { root, byLabel, refOverrides };
|
|
162
|
+
}
|
|
163
|
+
// ── cell parsing ───────────────────────────────────────────────────────────
|
|
164
|
+
/** Parse a property value like `<&gpioa 5 GPIO_ACTIVE_LOW>` into tokens. */
|
|
165
|
+
function parseCells(value) {
|
|
166
|
+
return value
|
|
167
|
+
// Angle-bracket cell wrappers are punctuation, not content.
|
|
168
|
+
.replace(/[<>]/g, ' ')
|
|
169
|
+
// Comment markers may carry multi-word labels ('D14 / A0', 'Pin 1,
|
|
170
|
+
// LEDK') — shield their internal whitespace AND commas from the split,
|
|
171
|
+
// restoring each to its own character afterwards (collapsing a comma
|
|
172
|
+
// to a space would hide it from the label splitter, and an unshielded
|
|
173
|
+
// comma tore the marker apart mid-token — '⟨Pin 1' + 'LEDK⟩' — whose
|
|
174
|
+
// truncated form minted garbage labels like 'Pin').
|
|
175
|
+
.replace(/⟨[^⟩]*⟩/g, (m) => ' ' + m.replace(/,/g, '\x1f').replace(/\s+/g, '\x1e') + ' ')
|
|
176
|
+
.split(/[\s,]+/)
|
|
177
|
+
.map((s) => s.trim().replace(/\x1f/g, ',').replace(/\x1e/g, ' '))
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
}
|
|
180
|
+
function parseGpioMap(value, parentCells) {
|
|
181
|
+
const toks = parseCells(value);
|
|
182
|
+
const entries = [];
|
|
183
|
+
let i = 0;
|
|
184
|
+
let pendingLabel;
|
|
185
|
+
const splitNames = (raw) =>
|
|
186
|
+
// '/' separates co-names of one pad ('D14 / A0'); ',' separates a pin
|
|
187
|
+
// number from its net name ('Pin 1, LEDK' → 'LEDK'). Whitespace is NOT
|
|
188
|
+
// a separator — 'Pin 1' must not collapse to 'Pin'.
|
|
189
|
+
raw.split(/[\/,]/).map((s) => s.trim()).filter((s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s));
|
|
190
|
+
while (i < toks.length) {
|
|
191
|
+
// Sweep up any ⟨label⟩ markers preceding the numeric entry.
|
|
192
|
+
while (i < toks.length && toks[i].startsWith('⟨')) {
|
|
193
|
+
const inner = toks[i].slice(1, -1).trim();
|
|
194
|
+
if (inner)
|
|
195
|
+
pendingLabel = inner;
|
|
196
|
+
i++;
|
|
197
|
+
}
|
|
198
|
+
const spec = toks[i];
|
|
199
|
+
if (spec === undefined)
|
|
200
|
+
break;
|
|
201
|
+
let childSpec = Number(spec);
|
|
202
|
+
// Macro child specifiers (the nano header's ARDUINO_NANO_HEADER_D14):
|
|
203
|
+
// the numeric suffix is the specifier, the alphabetic suffix carries the
|
|
204
|
+
// silkscreen name when no comment label exists.
|
|
205
|
+
let macroLabel;
|
|
206
|
+
if (!Number.isFinite(childSpec)) {
|
|
207
|
+
// Multi-word macros (ARDUINO_NANO_HEADER_D14): the LAST underscore
|
|
208
|
+
// splits the prefix from the <letters><digits> silkscreen suffix.
|
|
209
|
+
const m = spec.match(/^([A-Za-z][A-Za-z0-9_]*)_([A-Za-z]+)(\d+)$/);
|
|
210
|
+
if (m) {
|
|
211
|
+
childSpec = Number(m[3]);
|
|
212
|
+
macroLabel = `${m[2]}${m[3]}`;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
i++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// child-spec, pad, then &ctrl
|
|
220
|
+
const ctrl = toks[i + 2];
|
|
221
|
+
if (ctrl === undefined || !ctrl.startsWith('&')) {
|
|
222
|
+
i++;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const pin = Number(toks[i + 3]);
|
|
226
|
+
// Macro/expression pins (RTIO routing etc.) cannot be placed on the HAL
|
|
227
|
+
// pin map — skip the entry, keep scanning.
|
|
228
|
+
if (!Number.isFinite(pin)) {
|
|
229
|
+
i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const flags = [];
|
|
233
|
+
for (let f = i + 4; f < i + 3 + parentCells + 1 && f < toks.length; f++) {
|
|
234
|
+
const t = toks[f];
|
|
235
|
+
if (t.startsWith('&') || Number.isFinite(Number(t)))
|
|
236
|
+
break;
|
|
237
|
+
if (!t.startsWith('⟨'))
|
|
238
|
+
flags.push(t);
|
|
239
|
+
}
|
|
240
|
+
// A comment can name one pad twice ('D14 / A0' — the digital and analog
|
|
241
|
+
// silkscreen of the same pad): first name is the primary label, the rest
|
|
242
|
+
// ride as altLabels.
|
|
243
|
+
const names = pendingLabel ? splitNames(pendingLabel) : [];
|
|
244
|
+
const label = names[0] ?? macroLabel;
|
|
245
|
+
const altLabels = names.slice(1);
|
|
246
|
+
entries.push({
|
|
247
|
+
childSpecifier: childSpec,
|
|
248
|
+
...(label ? { label } : {}),
|
|
249
|
+
...(altLabels.length > 0 ? { altLabels } : {}),
|
|
250
|
+
ref: { controller: ctrl.slice(1), pin, flags },
|
|
251
|
+
});
|
|
252
|
+
pendingLabel = undefined;
|
|
253
|
+
// advance: spec + pad + &ctrl + parentCells numbers
|
|
254
|
+
i = i + 3 + parentCells;
|
|
255
|
+
// sweep trailing markers before the next entry
|
|
256
|
+
while (i < toks.length && toks[i].startsWith('⟨')) {
|
|
257
|
+
const inner = toks[i].slice(1, -1).trim();
|
|
258
|
+
if (inner && entries.length > 0) {
|
|
259
|
+
const trailing = splitNames(inner);
|
|
260
|
+
const last = entries[entries.length - 1];
|
|
261
|
+
if (trailing.length > 0 && !last.label)
|
|
262
|
+
last.label = trailing[0];
|
|
263
|
+
if (trailing.length > 1)
|
|
264
|
+
last.altLabels = [...(last.altLabels ?? []), ...trailing.slice(1)];
|
|
265
|
+
}
|
|
266
|
+
i++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return entries;
|
|
270
|
+
}
|
|
271
|
+
// ── board facts extraction ─────────────────────────────────────────────────
|
|
272
|
+
function gpioRefFromProp(value) {
|
|
273
|
+
const toks = parseCells(value);
|
|
274
|
+
// Expect: &ctrl pin [flags...]
|
|
275
|
+
if (toks.length < 2 || !toks[0].startsWith('&'))
|
|
276
|
+
return undefined;
|
|
277
|
+
const pin = Number(toks[1]);
|
|
278
|
+
if (!Number.isFinite(pin))
|
|
279
|
+
return undefined;
|
|
280
|
+
// Normalize flag tokens: strip grouping parens, drop '|' separators.
|
|
281
|
+
const flags = toks
|
|
282
|
+
.slice(2)
|
|
283
|
+
.filter((t) => !t.startsWith('⟨') && t !== '|')
|
|
284
|
+
.map((t) => t.replace(/[()]/g, ''))
|
|
285
|
+
.filter(Boolean);
|
|
286
|
+
return { controller: toks[0].slice(1), pin, flags };
|
|
287
|
+
}
|
|
288
|
+
// ── silicon pinctrl harvest ────────────────────────────────────────────────
|
|
289
|
+
// Per-family route grammars live in the PINCTRL_DIALECTS table below (each
|
|
290
|
+
// name/macro token encodes the complete route — source/channel/port/bit).
|
|
291
|
+
// The name↔value cross-check in lintNameValueAgreement covers each family's
|
|
292
|
+
// VALUE grammar (the pinmux macro's port/bit tuple).
|
|
293
|
+
// Labeled PWM controller device nodes (nRF `pwm0: pwm@4001c000` — any GPIO
|
|
294
|
+
// pad can carry any of a peripheral's four psel-routed channels, the matrix
|
|
295
|
+
// the manifest generator synthesizes from). ESP's ledc0 keeps its own
|
|
296
|
+
// LEDC_NODE_RE; STM32's pwm children are label-less and never match.
|
|
297
|
+
const PWM_NODE_RE = /(pwm\d+):\s*pwm@[0-9a-f]+\s*\{/g;
|
|
298
|
+
// ── NXP i.MX RT pinctrl dtsi (node-name shape, STM32's form) ───────────────
|
|
299
|
+
// `iomuxc_<pad>_adc1_in1: …` — the pad-to-GPIO join is in-band: every pad
|
|
300
|
+
// also declares `iomuxc_<pad>_gpio<port>_io<pin>`. FlexPWM routes the two
|
|
301
|
+
// subchannel outputs (`pwma`/`pwmb`, channel 0/1 of the DT child
|
|
302
|
+
// `flexpwm<N>_pwm<K>`); the complementary `pwmx` is skipped. This family is
|
|
303
|
+
// NOT in PINCTRL_DIALECTS — its routes resolve through a two-phase join.
|
|
304
|
+
//
|
|
305
|
+
// Two FlexPWM node-name conventions exist across the RT parts (both
|
|
306
|
+
// harvested): the classic rt10xx `…_flexpwm2_pwma3`, and the newer
|
|
307
|
+
// rt11xx/rt116x/rt118x `…_flexpwm1_pwm0_a` (pwm<K>_<a|b>, A→0/B→1; the `_x`
|
|
308
|
+
// complementary stays out, mirroring `pwmx`). The newer parts' ADC is the
|
|
309
|
+
// LPADC (`lpadc<N>`) — a different driver with a differential `ch<N>a|b`
|
|
310
|
+
// input-pair channel model — and stays OUT like the LPC55 lpadc, pending a
|
|
311
|
+
// dedicated channel-model gate.
|
|
312
|
+
const IMX_GPIO_JOIN_RE = /iomuxc_([a-z0-9_]+)_gpio(\d+)_io(\d+):/g;
|
|
313
|
+
const IMX_ADC_RE = /(iomuxc_[a-z0-9_]+_adc(\d+)_in(\d+)):\s*\w+\s*\{[^}]*?pinmux/g;
|
|
314
|
+
const IMX_PWM_RE = /(iomuxc_[a-z0-9_]+_flexpwm(\d+)_pwm([ab])(\d+)):\s*\w+\s*\{[^}]*?pinmux/g;
|
|
315
|
+
const IMX_PWM2_RE = /(iomuxc_[a-z0-9_]+_flexpwm(\d+)_pwm(\d+)_([ab])):\s*\w+\s*\{[^}]*?pinmux/g;
|
|
316
|
+
const PWM = 'pwm';
|
|
317
|
+
const ADC = 'adc';
|
|
318
|
+
const DAC = 'dac';
|
|
319
|
+
/** The self-contained route grammars, one per family×function. */
|
|
320
|
+
const PINCTRL_DIALECTS = [
|
|
321
|
+
// STM32 vendor-HAL pinctrl dtsi node shape (all SoC families):
|
|
322
|
+
// /omit-if-no-ref/ tim4_ch1_pb6: tim4_ch1_pb6 { pinmux = <STM32_PINMUX('B', 6, AF2)>; };
|
|
323
|
+
// The node NAME is captured verbatim (the overlay reference); the pinmux
|
|
324
|
+
// macro carries the authoritative port/bit for the cross-check. The F1
|
|
325
|
+
// family (AFIO model) spells the macro STM32F1_PINMUX — covered by the
|
|
326
|
+
// optional F<unit> in the value grammar.
|
|
327
|
+
{ kind: PWM, re: /\/omit-if-no-ref\/?\s+(tim(\d+)_ch(\d+)_p[a-z]\d+):\s*\w+\s*\{[^}]*?pinmux\s*=\s*<STM32(?:F\d+)?_PINMUX\('([A-Z])',\s*(\d+)/g, token: (m) => m[1], source: (m) => `tim${m[2]}`, channel: (m) => Number(m[3]), port: (m) => m[4], bit: (m) => Number(m[5]) },
|
|
328
|
+
// STM32F1 (AFIO) PWM output — `tim1_ch1_pwm_out_pa8`. The `_pwm_in_`
|
|
329
|
+
// (input capture), `_remapN_` (AFIO remap) and `_n` (complementary)
|
|
330
|
+
// spellings are excluded by the token grammar, mirroring the pwmx/inn rules.
|
|
331
|
+
{ kind: PWM, re: /\/omit-if-no-ref\/?\s+(tim(\d+)_ch(\d+)_pwm_out_p[a-z]\d+):\s*\w+\s*\{[^}]*?pinmux\s*=\s*<STM32F?\d*_PINMUX\('([A-Z])',\s*(\d+)/g, token: (m) => m[1], source: (m) => `tim${m[2]}`, channel: (m) => Number(m[3]), port: (m) => m[4], bit: (m) => Number(m[5]) },
|
|
332
|
+
// NXP Kinetis per-part header macros: FTM0_CH5_PTA0 (FTM = the pwm DT label).
|
|
333
|
+
{ kind: PWM, re: /#define FTM(\d+)_CH(\d+)_PT([A-Z])(\d+)\b/g, token: (m) => `FTM${m[1]}_CH${m[2]}_PT${m[3]}${m[4]}`, source: (m) => `ftm${m[1]}`, channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
334
|
+
// NXP LPC55 CTIMER match outputs (`ctimerN` = the DT label; MATCHn = ch n).
|
|
335
|
+
{ kind: PWM, re: /#define CTIMER(\d+)_MATCH(\d+)_PIO(\d+)_(\d+)\b/g, token: (m) => `CTIMER${m[1]}_MATCH${m[2]}_PIO${m[3]}_${m[4]}`, source: (m) => `ctimer${m[1]}`, channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
336
|
+
// GigaDevice GD32 TIMER macros (TIMER N is the DT timer label verbatim).
|
|
337
|
+
{ kind: PWM, re: /#define TIMER(\d+)_CH(\d+)_P([A-Z])(\d+)\b/g, token: (m) => `TIMER${m[1]}_CH${m[2]}_P${m[3]}${m[4]}`, source: (m) => `timer${m[1]}`, channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
338
|
+
// GigaDevice GD32 ADC — the header macros are package-specific (the board's
|
|
339
|
+
// include chain reaches its own part's header), so the header is the
|
|
340
|
+
// authoritative source, not the pinconfig YAML (whose package key is absent
|
|
341
|
+
// from the GD32 soc segment). All unit spellings: ADC_IN (digitless),
|
|
342
|
+
// ADC0/1/2_IN (single unit), ADC01_IN / ADC012_IN (shared — primary adc0).
|
|
343
|
+
{ kind: ADC, re: /#define ADC(\d*)_IN(\d+)_P([A-Z])(\d+)\b/g, token: (m) => `ADC${m[1]}_IN${m[2]}_P${m[3]}${m[4]}`, source: (m) => `adc${m[1] ? m[1][0] : ''}`, channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
344
|
+
// STM32 ADC — three vendor spellings: adc1_in1_pa1 (multi-unit),
|
|
345
|
+
// adc_in0_pa0 (single-unit, no unit digit), adc1_inp16_pa0 (H7 positive).
|
|
346
|
+
{ kind: ADC, re: /\/omit-if-no-ref\/?\s+(adc(\d*)_(?:in|inp)(\d+)_p[a-z]\d+):\s*\w+\s*\{[^}]*?pinmux\s*=\s*<STM32(?:F\d+)?_PINMUX\('([A-Z])',\s*(\d+)/g, token: (m) => m[1], source: (m) => `adc${m[2]}`, channel: (m) => Number(m[3]), port: (m) => m[4], bit: (m) => Number(m[5]) },
|
|
347
|
+
// NXP Kinetis ADC16 single-ended (source `adcN` = the DT label).
|
|
348
|
+
{ kind: ADC, re: /#define ADC(\d+)_SE(\d+)_PT([A-Z])(\d+)\b/g, token: (m) => `ADC${m[1]}_SE${m[2]}_PT${m[3]}${m[4]}`, source: (m) => `adc${m[1]}`, channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
349
|
+
// STM32 DAC — dac1_out1_pa4 (the pinctrl source IS the DT nodelabel).
|
|
350
|
+
{ kind: DAC, re: /\/omit-if-no-ref\/?\s+(dac(\d+)_out(\d+)_p[a-z]\d+):\s*\w+\s*\{[^}]*?pinmux\s*=\s*<STM32(?:F\d+)?_PINMUX\('([A-Z])',\s*(\d+)/g, token: (m) => m[1], source: (m) => `dac${m[2]}`, channel: (m) => Number(m[3]), port: (m) => m[4], bit: (m) => Number(m[5]) },
|
|
351
|
+
// STM32F1 (AFIO) DAC — `dac_out1_pa4` (digitless unit in the name, like the
|
|
352
|
+
// F1 ADC; the DT nodelabel is the conventional dac1).
|
|
353
|
+
{ kind: DAC, re: /\/omit-if-no-ref\/?\s+(dac_out(\d+)_p[a-z]\d+):\s*\w+\s*\{[^}]*?pinmux\s*=\s*<STM32F?\d*_PINMUX\('([A-Z])',\s*(\d+)/g, token: (m) => m[1], source: (m) => 'dac1', channel: (m) => Number(m[2]), port: (m) => m[3], bit: (m) => Number(m[4]) },
|
|
354
|
+
];
|
|
355
|
+
/** Harvest silicon routes from one pinctrl source text. Families by source
|
|
356
|
+
* shape: STM32/GD32/Kinetis/LPC C-header macros + i.MX RT dtsi node names;
|
|
357
|
+
* other families contribute nothing yet — see the family-convention notes
|
|
358
|
+
* in the manifest generator. Deduplicates by pinctrl node name / macro. */
|
|
359
|
+
function harvestPinctrlPins(src, pwm, adc, dac) {
|
|
360
|
+
const target = { pwm, adc, dac };
|
|
361
|
+
for (const d of PINCTRL_DIALECTS) {
|
|
362
|
+
d.re.lastIndex = 0;
|
|
363
|
+
let m;
|
|
364
|
+
while ((m = d.re.exec(src))) {
|
|
365
|
+
const token = d.token(m);
|
|
366
|
+
const map = target[d.kind];
|
|
367
|
+
if (map.has(token))
|
|
368
|
+
continue;
|
|
369
|
+
map.set(token, {
|
|
370
|
+
source: d.source(m),
|
|
371
|
+
channel: d.channel(m),
|
|
372
|
+
port: d.port(m),
|
|
373
|
+
bit: d.bit(m),
|
|
374
|
+
pinctrl: token,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// ── NXP i.MX RT pinctrl dtsi (node-name shape) ──────────────────────────
|
|
379
|
+
// `iomuxc_<pad>_adc1_in1: …` — the pad-to-GPIO join is in-band: every pad
|
|
380
|
+
// also declares `iomuxc_<pad>_gpio<port>_io<pin>`. FlexPWM routes the two
|
|
381
|
+
// subchannel outputs (`pwma`/`pwmb`, channel 0/1 of the DT child
|
|
382
|
+
// `flexpwm<N>_pwm<K>`); the complementary `pwmx` is skipped. The join map
|
|
383
|
+
// is built first, then the routes resolve against it.
|
|
384
|
+
const imxPadToGpio = new Map();
|
|
385
|
+
IMX_GPIO_JOIN_RE.lastIndex = 0;
|
|
386
|
+
let jm;
|
|
387
|
+
while ((jm = IMX_GPIO_JOIN_RE.exec(src))) {
|
|
388
|
+
if (!imxPadToGpio.has(jm[1]))
|
|
389
|
+
imxPadToGpio.set(jm[1], { port: jm[2], bit: Number(jm[3]) });
|
|
390
|
+
}
|
|
391
|
+
IMX_PWM_RE.lastIndex = 0;
|
|
392
|
+
while ((jm = IMX_PWM_RE.exec(src))) {
|
|
393
|
+
const pad = jm[1].replace(/^iomuxc_/, '').replace(/_flexpwm\d+_pwm[ab]\d+$/, '');
|
|
394
|
+
const gpio = imxPadToGpio.get(pad);
|
|
395
|
+
if (!gpio || pwm.has(jm[1]))
|
|
396
|
+
continue;
|
|
397
|
+
pwm.set(jm[1], {
|
|
398
|
+
source: `flexpwm${jm[2]}_pwm${jm[4]}`,
|
|
399
|
+
channel: jm[3] === 'b' ? 1 : 0,
|
|
400
|
+
port: gpio.port,
|
|
401
|
+
bit: gpio.bit,
|
|
402
|
+
pinctrl: jm[1],
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// NEW FlexPWM spelling: `…_flexpwm1_pwm0_a` (pwm<K>_<a|b>, channel a=0/b=1).
|
|
406
|
+
IMX_PWM2_RE.lastIndex = 0;
|
|
407
|
+
while ((jm = IMX_PWM2_RE.exec(src))) {
|
|
408
|
+
const pad = jm[1].replace(/^iomuxc_/, '').replace(/_flexpwm\d+_pwm\d+_[ab]$/, '');
|
|
409
|
+
const gpio = imxPadToGpio.get(pad);
|
|
410
|
+
if (!gpio || pwm.has(jm[1]))
|
|
411
|
+
continue;
|
|
412
|
+
pwm.set(jm[1], {
|
|
413
|
+
source: `flexpwm${jm[2]}_pwm${jm[3]}`,
|
|
414
|
+
channel: jm[4] === 'b' ? 1 : 0,
|
|
415
|
+
port: gpio.port,
|
|
416
|
+
bit: gpio.bit,
|
|
417
|
+
pinctrl: jm[1],
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
IMX_ADC_RE.lastIndex = 0;
|
|
421
|
+
while ((jm = IMX_ADC_RE.exec(src))) {
|
|
422
|
+
const pad = jm[1].replace(/^iomuxc_/, '').replace(/_adc\d+_in\d+$/, '');
|
|
423
|
+
const gpio = imxPadToGpio.get(pad);
|
|
424
|
+
if (!gpio || adc.has(jm[1]))
|
|
425
|
+
continue;
|
|
426
|
+
adc.set(jm[1], {
|
|
427
|
+
source: `adc${jm[2]}`,
|
|
428
|
+
channel: Number(jm[3]),
|
|
429
|
+
port: gpio.port,
|
|
430
|
+
bit: gpio.bit,
|
|
431
|
+
pinctrl: jm[1],
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Cross-validate every harvested route's NAME against its pinmux VALUE —
|
|
437
|
+
* both encode port/bit, independently (the name grammar and the macro's
|
|
438
|
+
* argument tuple). A disagreement means either a harvest regex bug or an
|
|
439
|
+
* upstream typo, and the route IS dropped: a silently-wrong fact is worse
|
|
440
|
+
* than a missing one. Per-family value extractors; a route whose value form
|
|
441
|
+
* is unknown is left unchecked (tolerant — the harvest stands).
|
|
442
|
+
*/
|
|
443
|
+
function lintNameValueAgreement(src, pwm, adc, dac) {
|
|
444
|
+
const warnings = [];
|
|
445
|
+
// The port/bit each family's NAME grammar encodes as its trailing pad
|
|
446
|
+
// (STM32 `…_pa1`; Kinetis `…_PTB0`; LPC `…_PIO0_10`; GD32 `…_PA0`).
|
|
447
|
+
const namePad = (name) => {
|
|
448
|
+
let m = name.match(/_p([a-z])(\d+)$/);
|
|
449
|
+
if (m)
|
|
450
|
+
return { port: m[1].toUpperCase(), bit: Number(m[2]) };
|
|
451
|
+
m = name.match(/_PT([A-Z])(\d+)$/);
|
|
452
|
+
if (m)
|
|
453
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
454
|
+
m = name.match(/_PIO(\d+)_(\d+)$/);
|
|
455
|
+
if (m)
|
|
456
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
457
|
+
m = name.match(/_P([A-Z])(\d+)$/);
|
|
458
|
+
if (m)
|
|
459
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
460
|
+
return undefined;
|
|
461
|
+
};
|
|
462
|
+
// The port/bit each family's VALUE macro carries. The value text sits
|
|
463
|
+
// within a small window after the name (same line for STM32/Kinetis/LPC,
|
|
464
|
+
// a backslash continuation for GD32).
|
|
465
|
+
const valuePad = (after) => {
|
|
466
|
+
let m = after.match(/STM32(?:F\d+)?_PINMUX\('([A-Z])',\s*(\d+)/);
|
|
467
|
+
if (m)
|
|
468
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
469
|
+
m = after.match(/KINETIS_MUX\('([A-Z])',\s*(\d+)/);
|
|
470
|
+
if (m)
|
|
471
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
472
|
+
m = after.match(/GD32_PINMUX_AFIO\('([A-Z])',\s*(\d+)/);
|
|
473
|
+
if (m)
|
|
474
|
+
return { port: m[1], bit: Number(m[2]) };
|
|
475
|
+
// IOCON_MUX(pin, type, func) — the GLOBAL pin index across LPC's 32-wide
|
|
476
|
+
// ports (PIO1_0 = 32), decoded back to port/bit.
|
|
477
|
+
m = after.match(/IOCON_MUX\((\d+),/);
|
|
478
|
+
if (m) {
|
|
479
|
+
const g = Number(m[1]);
|
|
480
|
+
return { port: String(Math.floor(g / 32)), bit: g % 32 };
|
|
481
|
+
}
|
|
482
|
+
return undefined;
|
|
483
|
+
};
|
|
484
|
+
const check = (name, route) => {
|
|
485
|
+
const byName = namePad(name);
|
|
486
|
+
if (!byName)
|
|
487
|
+
return false;
|
|
488
|
+
// Word-boundary occurrence: a plain indexOf would prefix-match a longer
|
|
489
|
+
// sibling (tim1_ch1_pb1 inside tim1_ch1_pb15) and cross-check the wrong
|
|
490
|
+
// node's macro. Dtsi labels carry `name:`; header macros `name `.
|
|
491
|
+
const re = new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[:\\s]`);
|
|
492
|
+
const hit = re.exec(src);
|
|
493
|
+
if (!hit)
|
|
494
|
+
return false;
|
|
495
|
+
const idx = hit.index;
|
|
496
|
+
// The value window must not cross into the NEXT definition (header
|
|
497
|
+
// `#define` or dtsi `/omit-if-no-ref/` node) — a foreign macro later in
|
|
498
|
+
// the window would cross-check against the wrong route. GD32's value
|
|
499
|
+
// sits on a backslash continuation line, inside the same boundary.
|
|
500
|
+
const raw = src.slice(idx, idx + 240);
|
|
501
|
+
let end = raw.length;
|
|
502
|
+
const nextDefine = raw.indexOf('\n#define', 1);
|
|
503
|
+
if (nextDefine > 0)
|
|
504
|
+
end = Math.min(end, nextDefine);
|
|
505
|
+
const nextNode = raw.indexOf('/omit-if-no-ref', 8);
|
|
506
|
+
if (nextNode > 0)
|
|
507
|
+
end = Math.min(end, nextNode);
|
|
508
|
+
const byValue = valuePad(raw.slice(0, end));
|
|
509
|
+
if (!byValue)
|
|
510
|
+
return false;
|
|
511
|
+
const portOk = byValue.port === byName.port;
|
|
512
|
+
if (!portOk || byValue.bit !== byName.bit) {
|
|
513
|
+
warnings.push(`pinctrl '${name}': name says ${byName.port}${byName.bit} but its pinmux value says ` +
|
|
514
|
+
`${byValue.port}${byValue.bit} — route dropped as untrustworthy (harvest bug or upstream typo).`);
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
return false;
|
|
518
|
+
};
|
|
519
|
+
for (const [name, r] of [...pwm])
|
|
520
|
+
if (check(name, r))
|
|
521
|
+
pwm.delete(name);
|
|
522
|
+
for (const [name, r] of [...adc])
|
|
523
|
+
if (check(name, r))
|
|
524
|
+
adc.delete(name);
|
|
525
|
+
for (const [name, r] of [...dac])
|
|
526
|
+
if (check(name, r))
|
|
527
|
+
dac.delete(name);
|
|
528
|
+
return warnings;
|
|
529
|
+
}
|
|
530
|
+
// A watchdog device node anywhere in the include chain (SoC dtsi shape:
|
|
531
|
+
// `iwdg: watchdog@40003000 { compatible = "st,stm32-watchdog"; }` — nRF
|
|
532
|
+
// `wdt0: watchdog@40010200 { compatible = "nordic,nrf-watchdog"; }`). The
|
|
533
|
+
// nodelabel is the fact; compatible confirms it is a watchdog.
|
|
534
|
+
const WDT_NODE_RE = /(\w+):\s*watchdog@[0-9a-f]+\s*\{[^}]*?compatible\s*=\s*"([-\w,]*watchdog[-\w,]*)"/g;
|
|
535
|
+
// ESP32 LEDC matrix controller node (esp32s3_common.dtsi:
|
|
536
|
+
// ledc0: ledc@60019000 { compatible = "espressif,esp32-ledc"; }).
|
|
537
|
+
const LEDC_NODE_RE = /(ledc\d+):\s*ledc@[0-9a-f]+\s*\{/g;
|
|
538
|
+
// Flash node size (flash0: flash@8000000 { reg = <0x08000000 DT_SIZE_K(512)>; }).
|
|
539
|
+
const FLASH_KB_RE = /flash\d*:\s*flash@[0-9a-f]+\s*\{[^}]*?DT_SIZE_([KM])\((\d+)\)/g;
|
|
540
|
+
// Variant-module &-override form (Espressif): `&flash0 { reg = <0x0 DT_SIZE_M(8)>; }`
|
|
541
|
+
const FLASH_OVERRIDE_KB_RE = /&flash\d*\s*\{[^}]*?reg\s*=\s*<[^>]*?DT_SIZE_([KM])\((\d+)\)>/g;
|
|
542
|
+
// Counter-capable devices. The Zephyr counter driver binds either a labeled
|
|
543
|
+
// node whose compatible IS a counter (nRF `rtc1: rtc@… nordic,nrf-rtc`;
|
|
544
|
+
// STM32 `rtc: rtc@… st,stm32-rtc` — the RTC bindings carry counter drivers)
|
|
545
|
+
// or an unlabeled `counter { compatible = "…-counter" }` child under a
|
|
546
|
+
// labeled timer parent (ESP32 timer0-2, Ambiq, STM32 timer counters).
|
|
547
|
+
// COUNTER_COMPATS mirrors dts/bindings/counter/* plus the two rtc compatibles
|
|
548
|
+
// with counter drivers (they live under bindings/rtc).
|
|
549
|
+
const COUNTER_COMPATS = new Set([
|
|
550
|
+
'nordic,nrf-rtc', 'st,stm32-rtc',
|
|
551
|
+
]);
|
|
552
|
+
// Child form: (parent label): …timer-ish node… { … counter { compatible = "X" … } }
|
|
553
|
+
const COUNTER_CHILD_RE = /(\w+):\s*(?:counter|timer|rtc)@[0-9a-f]+\s*\{(?:[^{}]|\{[^{}]*\})*?\bcounter\s*\{(?:[^{}]|\{[^{}]*\})*?compatible\s*=\s*"([-\w,]+)"/g;
|
|
554
|
+
// Self form: (label): node@… { … compatible = "X" … } where X ∈ COUNTER_COMPATS
|
|
555
|
+
const COUNTER_SELF_RE = /(\w+):\s*(?:rtc|counter|timer)@[0-9a-f]+\s*\{[^}]*?compatible\s*=\s*"([-\w,]+)"/g;
|
|
556
|
+
/** Harvest counter nodes from the merged include-chain text. */
|
|
557
|
+
function harvestCounterNodes(text) {
|
|
558
|
+
const out = [];
|
|
559
|
+
const seen = new Set();
|
|
560
|
+
let m;
|
|
561
|
+
COUNTER_CHILD_RE.lastIndex = 0;
|
|
562
|
+
while ((m = COUNTER_CHILD_RE.exec(text))) {
|
|
563
|
+
const key = `child:${m[1]}`;
|
|
564
|
+
if (!seen.has(key)) {
|
|
565
|
+
seen.add(key);
|
|
566
|
+
out.push({ compatible: m[2], parentLabel: m[1] });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
COUNTER_SELF_RE.lastIndex = 0;
|
|
570
|
+
while ((m = COUNTER_SELF_RE.exec(text))) {
|
|
571
|
+
if (!COUNTER_COMPATS.has(m[2]))
|
|
572
|
+
continue;
|
|
573
|
+
const key = `self:${m[1]}`;
|
|
574
|
+
if (!seen.has(key)) {
|
|
575
|
+
seen.add(key);
|
|
576
|
+
out.push({ compatible: m[2], nodeLabel: m[1] });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return out;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Read one board DTS file (following its local include chain) and extract
|
|
583
|
+
* the board-level facts. `zephyrBoardsRoot` (e.g. <zephyr>/boards) resolves
|
|
584
|
+
* `<../boards/common/...>` style includes; `moduleDtsRoots` resolve the
|
|
585
|
+
* vendor HAL modules' pinctrl dtsi (`<st/f4/...-pinctrl.dtsi>` lives under
|
|
586
|
+
* modules/hal/stm32/dts — Zephyr's MODULE_DTS_ROOTS, mirrored here).
|
|
587
|
+
*/
|
|
588
|
+
export function readBoardDts(dtsPath, opts = {}) {
|
|
589
|
+
const roots = {
|
|
590
|
+
angle: [...(opts.moduleDtsRoots ?? []), ...(opts.zephyrBoardsRoot ? [opts.zephyrBoardsRoot] : [])],
|
|
591
|
+
};
|
|
592
|
+
const seen = new Set();
|
|
593
|
+
const pwmPins = new Map();
|
|
594
|
+
const adcPins = new Map();
|
|
595
|
+
const dacPins = new Map();
|
|
596
|
+
const pinctrlWarnings = [];
|
|
597
|
+
let wdtSocNode;
|
|
598
|
+
let ledcNode;
|
|
599
|
+
let flashKb;
|
|
600
|
+
let hasStoragePartition;
|
|
601
|
+
let storageReg;
|
|
602
|
+
let text = '';
|
|
603
|
+
const absorb = (file) => {
|
|
604
|
+
const real = path.resolve(file);
|
|
605
|
+
if (seen.has(real))
|
|
606
|
+
return;
|
|
607
|
+
seen.add(real);
|
|
608
|
+
const src = fs.readFileSync(real, 'utf8');
|
|
609
|
+
// Silicon routes ride the pinctrl dtsi files pulled in by the board's
|
|
610
|
+
// include chain — harvested from every absorbed file (harmless no-op on
|
|
611
|
+
// files whose grammars don't match).
|
|
612
|
+
harvestPinctrlPins(src, pwmPins, adcPins, dacPins);
|
|
613
|
+
pinctrlWarnings.push(...lintNameValueAgreement(src, pwmPins, adcPins, dacPins));
|
|
614
|
+
// Expand includes textualy (depth bounded by `seen`).
|
|
615
|
+
const includeRe = /#include\s+([<"][^>"]+[>"])/g;
|
|
616
|
+
let last = 0;
|
|
617
|
+
let m;
|
|
618
|
+
let out = '';
|
|
619
|
+
while ((m = includeRe.exec(src))) {
|
|
620
|
+
out += src.slice(last, m.index);
|
|
621
|
+
const resolved = resolveInclude(m[1], real, roots);
|
|
622
|
+
if (resolved)
|
|
623
|
+
absorb(resolved);
|
|
624
|
+
last = includeRe.lastIndex;
|
|
625
|
+
}
|
|
626
|
+
out += src.slice(last);
|
|
627
|
+
text += out + '\n';
|
|
628
|
+
};
|
|
629
|
+
absorb(dtsPath);
|
|
630
|
+
// SoC-level watchdog node: first watchdog-compatible node in the include
|
|
631
|
+
// chain (a board-level watchdog0 alias, checked by the walker, wins).
|
|
632
|
+
WDT_NODE_RE.lastIndex = 0;
|
|
633
|
+
const wdtMatch = WDT_NODE_RE.exec(text);
|
|
634
|
+
if (wdtMatch)
|
|
635
|
+
wdtSocNode = wdtMatch[1];
|
|
636
|
+
// SoC-level ADC/DAC device nodelabels (adc1:/dac1: device nodes). The
|
|
637
|
+
// pinctrl files sometimes carry routes for peripherals the SoC dtsi never
|
|
638
|
+
// declares (L4S5: dac1_out1_pa4 exists, no dac1 device) — routes for an
|
|
639
|
+
// undeclared device are dropped by the manifest generator.
|
|
640
|
+
const analogDevices = new Set();
|
|
641
|
+
// Digitless labels included: samd2x names its single controller `adc:`
|
|
642
|
+
// (the pinconfigs YAML's source spelling) — without it in analogDevices,
|
|
643
|
+
// the non-empty cross-check dropped every Atmel route. The label — not the
|
|
644
|
+
// node name — is the semantic signal, so non-standard node names
|
|
645
|
+
// (`gau_adc0`, `eadc`, `adc_etc`) are accepted; the label grammar covers
|
|
646
|
+
// every ADC/DAC device naming in the tree: adc\d* / dac\d* (classic),
|
|
647
|
+
// lpadc\d* (NXP LPADC), eadc\d* (Nuvoton), sadc\d* (Renesas/various), and
|
|
648
|
+
// the underscore forms adc_\d+ / dac_\d+ (NXP MCX).
|
|
649
|
+
const DEV_RE = /\b((?:lpadc|eadc|sadc|adc|dac)\d*|(?:adc|dac)_\d+):\s*\w+@[0-9a-f]+\s*\{/g;
|
|
650
|
+
let dm;
|
|
651
|
+
while ((dm = DEV_RE.exec(text)))
|
|
652
|
+
analogDevices.add(dm[1]);
|
|
653
|
+
// ESP32 LEDC matrix controller node (any SoC dtsi in the chain declaring
|
|
654
|
+
// one — esp32s3_common.dtsi's ledc0).
|
|
655
|
+
LEDC_NODE_RE.lastIndex = 0;
|
|
656
|
+
const ledcMatch = LEDC_NODE_RE.exec(text);
|
|
657
|
+
if (ledcMatch)
|
|
658
|
+
ledcNode = ledcMatch[1];
|
|
659
|
+
// Labeled PWM controller nodes (nRF's psel-routed matrix peripherals).
|
|
660
|
+
const pwmNodes = new Set();
|
|
661
|
+
let pm;
|
|
662
|
+
PWM_NODE_RE.lastIndex = 0;
|
|
663
|
+
while ((pm = PWM_NODE_RE.exec(text)))
|
|
664
|
+
pwmNodes.add(pm[1]);
|
|
665
|
+
// Flash size (largest reg across the chain — the package variant dtsi
|
|
666
|
+
// overrides the SoC base with the real size). Two spellings: the SoC form
|
|
667
|
+
// `flash0: flash@... { reg = <... DT_SIZE_K(n)>; }` and the variant
|
|
668
|
+
// &-override `&flash0 { reg = <0x0 DT_SIZE_M(n)>; }` (Espressif WROOM
|
|
669
|
+
// modules). DT_SIZE_M carries megabytes.
|
|
670
|
+
for (const re of [FLASH_KB_RE, FLASH_OVERRIDE_KB_RE]) {
|
|
671
|
+
re.lastIndex = 0;
|
|
672
|
+
let fm;
|
|
673
|
+
while ((fm = re.exec(text))) {
|
|
674
|
+
// Group 1 is the DT_SIZE_ unit, group 2 the magnitude.
|
|
675
|
+
const kb = fm[1] === 'M' ? Number(fm[2]) * 1024 : Number(fm[2]);
|
|
676
|
+
if (!flashKb || kb > flashKb)
|
|
677
|
+
flashKb = kb;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
hasStoragePartition = /storage_partitions*:/.test(text);
|
|
681
|
+
// The board's own storage_partition reg (offset + size), when shipped —
|
|
682
|
+
// ESP32's AMP partition layout defines one; those boards carry their real
|
|
683
|
+
// facts instead of a synthesized region.
|
|
684
|
+
const sm = /storage_partitions*:\s*partition@[0-9a-f]+\s*\{[^}]*?reg\s*=\s*<0x([0-9a-f]+)\s+DT_SIZE_([KM])\((\d+)\)>/s.exec(text);
|
|
685
|
+
if (sm) {
|
|
686
|
+
const sizeKb = sm[2] === 'M' ? Number(sm[3]) * 1024 : Number(sm[3]);
|
|
687
|
+
storageReg = { offsetBytes: parseInt(sm[1], 16), sizeBytes: sizeKb * 1024 };
|
|
688
|
+
}
|
|
689
|
+
const counterNodes = harvestCounterNodes(text);
|
|
690
|
+
const { root: syntheticRoot, byLabel, refOverrides } = parseStatements(stripCommentsKeepMarkers(text));
|
|
691
|
+
// The DTS root ('/ { ... }') is a child of the synthetic outer node —
|
|
692
|
+
// resolve it so board-level nodes (aliases/chosen/leds/gpio_keys/&) sit at
|
|
693
|
+
// the depth the extractors expect.
|
|
694
|
+
const root = syntheticRoot.children.get('/') ?? syntheticRoot;
|
|
695
|
+
// aliases {} and chosen {} — phandle-ref properties
|
|
696
|
+
const aliases = {};
|
|
697
|
+
const chosen = {};
|
|
698
|
+
for (const name of ['aliases', 'chosen']) {
|
|
699
|
+
const node = root.children.get(name);
|
|
700
|
+
if (!node)
|
|
701
|
+
continue;
|
|
702
|
+
for (const [k, v] of node.props) {
|
|
703
|
+
const ref = v.match(/^&([\w,-]+)$/);
|
|
704
|
+
if (ref) {
|
|
705
|
+
if (name === 'aliases')
|
|
706
|
+
aliases[k] = ref[1];
|
|
707
|
+
else
|
|
708
|
+
chosen[k] = ref[1];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
// GPIO controller inventory: every labeled node with the universal
|
|
713
|
+
// `gpio-controller` property. The SoC dtsi (absorbed) declares the FULL
|
|
714
|
+
// port list; the board's facts only name the ports it wires. `ngpios`
|
|
715
|
+
// carries the width when the dtsi states one.
|
|
716
|
+
const gpioControllers = [];
|
|
717
|
+
for (const [label, node] of byLabel) {
|
|
718
|
+
if (!node.props.has('gpio-controller'))
|
|
719
|
+
continue;
|
|
720
|
+
const ngpiosRaw = node.props.get('ngpios');
|
|
721
|
+
const ngpios = ngpiosRaw !== undefined ? Number(ngpiosRaw.replace(/[<>]/g, '')) : undefined;
|
|
722
|
+
gpioControllers.push({
|
|
723
|
+
nodelabel: node.name.startsWith('&') ? node.name.slice(1) : label,
|
|
724
|
+
...(ngpios !== undefined && Number.isFinite(ngpios) ? { ngpios } : {}),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
gpioControllers.sort((a, b) => a.nodelabel.localeCompare(b.nodelabel));
|
|
728
|
+
// gpio-leds / gpio-keys children (match on compatible OR node name)
|
|
729
|
+
const leds = [];
|
|
730
|
+
const buttons = [];
|
|
731
|
+
const collectGpioNodes = (node) => {
|
|
732
|
+
const compat = node.props.get('compatible') ?? '';
|
|
733
|
+
const isLeds = compat.includes('gpio-leds');
|
|
734
|
+
const isKeys = compat.includes('gpio-keys');
|
|
735
|
+
if (isLeds || isKeys) {
|
|
736
|
+
// A label can be aliased several times (led0 + mcuboot-led0); collect
|
|
737
|
+
// ALL aliases per label, then prefer the canonical led<N>/sw<N> form.
|
|
738
|
+
const aliasesByLabel = new Map();
|
|
739
|
+
for (const [alias, label] of Object.entries(aliases)) {
|
|
740
|
+
const list = aliasesByLabel.get(label) ?? [];
|
|
741
|
+
list.push(alias);
|
|
742
|
+
aliasesByLabel.set(label, list);
|
|
743
|
+
}
|
|
744
|
+
for (const child of node.children.values()) {
|
|
745
|
+
const ref = gpioRefFromProp(child.props.get('gpios') ?? '');
|
|
746
|
+
if (!ref)
|
|
747
|
+
continue;
|
|
748
|
+
const nodelabel = child.labels[0] ?? child.name;
|
|
749
|
+
const aliasCandidates = child.labels.flatMap((l) => aliasesByLabel.get(l) ?? []);
|
|
750
|
+
const alias = aliasCandidates.find((a) => /^(led|sw|button)[0-9]*$/.test(a)) ?? aliasCandidates[0];
|
|
751
|
+
const entry = {
|
|
752
|
+
...ref,
|
|
753
|
+
nodelabel,
|
|
754
|
+
alias,
|
|
755
|
+
};
|
|
756
|
+
(isLeds ? leds : buttons).push(entry);
|
|
757
|
+
}
|
|
758
|
+
return; // do not recurse into a leds/keys container
|
|
759
|
+
}
|
|
760
|
+
for (const child of node.children.values())
|
|
761
|
+
collectGpioNodes(child);
|
|
762
|
+
};
|
|
763
|
+
collectGpioNodes(root);
|
|
764
|
+
// Connector nexus nodes: any node with a gpio-map property
|
|
765
|
+
const connectors = [];
|
|
766
|
+
const findConnectors = (node) => {
|
|
767
|
+
const map = node.props.get('gpio-map');
|
|
768
|
+
if (map) {
|
|
769
|
+
const cells = Number(node.props.get('#gpio-cells')?.replace(/[<>]/g, '') ?? '2');
|
|
770
|
+
const parentCells = Number.isFinite(cells) && cells > 0 ? cells : 2;
|
|
771
|
+
const entries = parseGpioMap(map, parentCells);
|
|
772
|
+
// The LAST entry's trailing /* label */ comment can land after the
|
|
773
|
+
// property's terminating ';' — its marker never enters the value. When
|
|
774
|
+
// labeled siblings share a prefix+number pattern (D0…D9), extend it to
|
|
775
|
+
// the unlabeled tail entry (D10) instead of falling back to p<N>.
|
|
776
|
+
const labeled = entries.filter((e) => e.label);
|
|
777
|
+
const pattern = labeled.length >= 2
|
|
778
|
+
? (() => {
|
|
779
|
+
const m = labeled[0].label.match(/^([A-Za-z]+)([0-9]+)$/);
|
|
780
|
+
if (!m)
|
|
781
|
+
return undefined;
|
|
782
|
+
const prefix = m[1];
|
|
783
|
+
const consistent = labeled.every((e) => e.label.startsWith(prefix) && Number(e.label.slice(prefix.length)) === e.childSpecifier);
|
|
784
|
+
return consistent ? prefix : undefined;
|
|
785
|
+
})()
|
|
786
|
+
: undefined;
|
|
787
|
+
const pins = {};
|
|
788
|
+
for (const e of entries) {
|
|
789
|
+
const label = e.label ?? (pattern ? `${pattern}${e.childSpecifier}` : `p${e.childSpecifier}`);
|
|
790
|
+
pins[label] = e.ref;
|
|
791
|
+
for (const alt of e.altLabels ?? [])
|
|
792
|
+
pins[alt] = e.ref;
|
|
793
|
+
}
|
|
794
|
+
// Complete the analog run when the FINAL entry's comment lands after
|
|
795
|
+
// the property's ';' (its marker never enters the value): the header's
|
|
796
|
+
// D/A pairing increments in lockstep (D14/A0 … D21/A7), so a consistent
|
|
797
|
+
// D−A offset observed on the captured entries extends to the tail.
|
|
798
|
+
const observed = entries.filter((e) => {
|
|
799
|
+
const d = e.label?.match(/^D(\d+)$/);
|
|
800
|
+
return d && e.altLabels?.some((a) => /^A\d+$/.test(a));
|
|
801
|
+
});
|
|
802
|
+
if (observed.length >= 2) {
|
|
803
|
+
const offsetOf = (e) => Number(e.label.slice(1)) - Number(e.altLabels.find((a) => /^A\d+$/.test(a)).slice(1));
|
|
804
|
+
const offset = offsetOf(observed[0]);
|
|
805
|
+
if (observed.every((e) => offsetOf(e) === offset)) {
|
|
806
|
+
for (const e of entries) {
|
|
807
|
+
const d = e.label?.match(/^D(\d+)$/);
|
|
808
|
+
if (!d || e.altLabels?.some((a) => /^A\d+$/.test(a)))
|
|
809
|
+
continue;
|
|
810
|
+
// Only positive A indices within the observed run — D0…D13 sit
|
|
811
|
+
// below the analog block (offset would go negative).
|
|
812
|
+
const aIdx = Number(d[1]) - offset;
|
|
813
|
+
if (aIdx < 0)
|
|
814
|
+
continue;
|
|
815
|
+
if (!pins[`A${aIdx}`])
|
|
816
|
+
pins[`A${aIdx}`] = e.ref;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
connectors.push({
|
|
821
|
+
nodelabel: node.labels[0] ?? node.name,
|
|
822
|
+
compatible: (node.props.get('compatible') ?? '').replace(/"/g, '') || undefined,
|
|
823
|
+
pins,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
for (const child of node.children.values())
|
|
827
|
+
findConnectors(child);
|
|
828
|
+
};
|
|
829
|
+
findConnectors(root);
|
|
830
|
+
// Connector io-channel-maps (the DKs' arduino,uno-adc nodes): entries
|
|
831
|
+
// `<idx &adc ch>` name the channel a CONNECTOR pin is wired to; the
|
|
832
|
+
// trailing /* A0 … */ comment names the pin. Joined against the gpio-map
|
|
833
|
+
// pins of the board's connectors by that label — board-authored wiring
|
|
834
|
+
// truth for SoCs whose silicon map has no other in-tree source.
|
|
835
|
+
const connectorAdc = [];
|
|
836
|
+
const ioEntries = [];
|
|
837
|
+
const findIoChannels = (node) => {
|
|
838
|
+
const map = node.props.get('io-channel-map');
|
|
839
|
+
if (map) {
|
|
840
|
+
// Labels ride the ⟨…⟩ comment markers (stripCommentsKeepMarkers) —
|
|
841
|
+
// the same channel the gpio-map parser reads silkscreen from. A `,`
|
|
842
|
+
// (mid-list) or `;` (last entry) may sit between the cell and its
|
|
843
|
+
// marker.
|
|
844
|
+
for (const m of map.matchAll(/<(\d+)\s+&([\w,-]+)\s+(\d+)\s*>[\s;,]*(?:⟨([A-Za-z_][\w-]*)?[^⟩]*⟩)?/g)) {
|
|
845
|
+
const channel = Number(m[3]);
|
|
846
|
+
if (!Number.isFinite(channel))
|
|
847
|
+
continue;
|
|
848
|
+
ioEntries.push({ ...(m[4] ? { label: m[4] } : {}), source: m[2], channel });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
for (const child of node.children.values())
|
|
852
|
+
findIoChannels(child);
|
|
853
|
+
};
|
|
854
|
+
findIoChannels(root);
|
|
855
|
+
for (const io of ioEntries) {
|
|
856
|
+
if (!io.label)
|
|
857
|
+
continue; // unlabeled entries can't be joined to a pad
|
|
858
|
+
for (const c of connectors) {
|
|
859
|
+
const ref = c.pins[io.label];
|
|
860
|
+
if (ref) {
|
|
861
|
+
connectorAdc.push({ source: io.source, channel: io.channel, controller: ref.controller, pin: ref.pin });
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// Addressable user LED (worldsemi,ws2812-*). Three wiring forms in the
|
|
867
|
+
// tree: the RP2040 PIO variant nests a plain `gpios = <&ctrl pin …>` node;
|
|
868
|
+
// the SPI/I2S variants sit on a bus whose pinctrl names the data pad in
|
|
869
|
+
// the macro itself (SPIM3_MOSI_GPIO33, I2S0_O_SD_GPIO48 — the OUTPUT SD
|
|
870
|
+
// is the data line; I_SD is capture, wrong direction). Walks the
|
|
871
|
+
// SYNTHETIC root: the strips ride &ref blocks (siblings of the board's
|
|
872
|
+
// `/ {}` node), which the resolved root's walkers never see.
|
|
873
|
+
const stripLed = findStripLed(syntheticRoot, byLabel);
|
|
874
|
+
// PWM-driven LEDs (pwm-leds children): `pwms = <&pwmN channel [period]
|
|
875
|
+
// [flags]>`. Only aliased children are addressable by the PWM lowering
|
|
876
|
+
// (DT_ALIAS(pwm_led0)); carry the alias through.
|
|
877
|
+
const reverseAlias = new Map();
|
|
878
|
+
for (const [name, label] of Object.entries(aliases))
|
|
879
|
+
reverseAlias.set(label, name);
|
|
880
|
+
const pwmLeds = [];
|
|
881
|
+
for (const ledsNode of root.children.values()) {
|
|
882
|
+
if ((ledsNode.props.get('compatible') ?? '').replace(/"/g, '') !== 'pwm-leds')
|
|
883
|
+
continue;
|
|
884
|
+
for (const child of ledsNode.children.values()) {
|
|
885
|
+
const pwms = child.props.get('pwms');
|
|
886
|
+
if (!pwms)
|
|
887
|
+
continue;
|
|
888
|
+
const toks = parseCells(pwms);
|
|
889
|
+
if (toks.length < 2 || !toks[0].startsWith('&'))
|
|
890
|
+
continue;
|
|
891
|
+
const channel = Number(toks[1]);
|
|
892
|
+
if (!Number.isFinite(channel))
|
|
893
|
+
continue;
|
|
894
|
+
const period = Number(toks[2]);
|
|
895
|
+
const flags = toks.slice(Number.isFinite(period) ? 3 : 2)
|
|
896
|
+
.filter((t) => !t.startsWith('⟨') && !Number.isFinite(Number(t)) && !/[()]/.test(t));
|
|
897
|
+
const led = {
|
|
898
|
+
controller: toks[0].slice(1),
|
|
899
|
+
channel,
|
|
900
|
+
...(Number.isFinite(period) ? { periodNs: period } : {}),
|
|
901
|
+
...(flags.length > 0 ? { flags } : {}),
|
|
902
|
+
...(child.labels[0] && reverseAlias.has(child.labels[0]) ? { alias: reverseAlias.get(child.labels[0]) } : {}),
|
|
903
|
+
};
|
|
904
|
+
pwmLeds.push(led);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// USB device wiring: the board's &usbd / zephyr_udc0 node status. The
|
|
908
|
+
// label form (`zephyr_udc0: &usbd { … }`) attaches under both names.
|
|
909
|
+
let usbDevice;
|
|
910
|
+
let usbController;
|
|
911
|
+
for (const key of ['zephyr_udc0', 'usbd']) {
|
|
912
|
+
const n = byLabel.get(key);
|
|
913
|
+
if (!n)
|
|
914
|
+
continue;
|
|
915
|
+
const status = n.props.get('status');
|
|
916
|
+
if (status === '"okay"') {
|
|
917
|
+
usbDevice = 'enabled';
|
|
918
|
+
usbController = key;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
if (status === '"disabled"' && usbDevice === undefined)
|
|
922
|
+
usbDevice = 'disabled';
|
|
923
|
+
}
|
|
924
|
+
// Bus controller nodelabels the board wires up: &ref blocks whose status
|
|
925
|
+
// is okay (or that carry no status override but configure the bus — the
|
|
926
|
+
// app overlay decides enablement; cuttlefish's own overlay writes it).
|
|
927
|
+
// Classified by nodelabel — bus node names are vendor-stable (i2c0,
|
|
928
|
+
// spi2, usart0, eusart1, lpuart1, …).
|
|
929
|
+
const buses = { i2c: [], spi: [], uart: [] };
|
|
930
|
+
// Multi-function bus blocks whose nodelabel carries no function (Atmel SAM
|
|
931
|
+
// sercomN: the SAME controller can be I2C, SPI, or UART) are classified by
|
|
932
|
+
// their compatible (atmel,sam0-i2c / atmel,sam0-spi / atmel,sam0-uart).
|
|
933
|
+
const classifyBus = (label, compatible) => {
|
|
934
|
+
if (/^i2c|^twi/.test(label))
|
|
935
|
+
return 'i2c';
|
|
936
|
+
if (/^spi|^ssp/.test(label))
|
|
937
|
+
return 'spi';
|
|
938
|
+
if (/uart|usart/.test(label))
|
|
939
|
+
return 'uart';
|
|
940
|
+
const compat = compatible.replace(/^"|"$/g, '');
|
|
941
|
+
if (/-i2c$|,twi$/.test(compat))
|
|
942
|
+
return 'i2c';
|
|
943
|
+
if (/-spi$/.test(compat))
|
|
944
|
+
return 'spi';
|
|
945
|
+
if (/-uart$|-usart$/.test(compat))
|
|
946
|
+
return 'uart';
|
|
947
|
+
return undefined;
|
|
948
|
+
};
|
|
949
|
+
// Only labels the DTS OVERRODE with an &ref block count (`xiao_i2c: &i2c1
|
|
950
|
+
// { … }` → nodelabel 'i2c1'). With SoC dtsis absorbed, an override merges
|
|
951
|
+
// into the SoC's real labeled node (no synthetic '&i2c1' name), so the
|
|
952
|
+
// refOverrides set — not the node's name — carries the signal. Pinctrl
|
|
953
|
+
// groups ('i2c0_default') never appear in it; _default/_sleep is belt and
|
|
954
|
+
// braces.
|
|
955
|
+
const seenBuses = new Set();
|
|
956
|
+
for (const [label, node] of byLabel) {
|
|
957
|
+
if (!refOverrides.has(label))
|
|
958
|
+
continue;
|
|
959
|
+
if (label === 'usbd' || /_(default|sleep)$/.test(label))
|
|
960
|
+
continue;
|
|
961
|
+
const nodelabel = node.name.startsWith('&') ? node.name.slice(1) : label;
|
|
962
|
+
const kind = classifyBus(nodelabel, node.props.get('compatible') ?? '');
|
|
963
|
+
if (!kind || seenBuses.has(nodelabel))
|
|
964
|
+
continue;
|
|
965
|
+
const status = node.props.get('status');
|
|
966
|
+
if (status === '"disabled"')
|
|
967
|
+
continue;
|
|
968
|
+
seenBuses.add(nodelabel);
|
|
969
|
+
buses[kind].push(nodelabel);
|
|
970
|
+
}
|
|
971
|
+
for (const kind of ['i2c', 'spi', 'uart'])
|
|
972
|
+
buses[kind].sort();
|
|
973
|
+
void byLabel; // (labels already captured during parse)
|
|
974
|
+
return {
|
|
975
|
+
aliases, chosen, leds, buttons, connectors, connectorAdc, pwmLeds, buses,
|
|
976
|
+
...(pinctrlWarnings.length > 0 ? { pinctrlWarnings } : {}),
|
|
977
|
+
pwmPins: [...pwmPins.values()],
|
|
978
|
+
adcPins: [...adcPins.values()],
|
|
979
|
+
dacPins: [...dacPins.values()],
|
|
980
|
+
analogDevices: [...analogDevices],
|
|
981
|
+
...(wdtSocNode ? { wdtSocNode } : {}),
|
|
982
|
+
...(ledcNode ? { ledcNode } : {}),
|
|
983
|
+
...(pwmNodes.size > 0 ? { pwmNodes: [...pwmNodes].sort() } : {}),
|
|
984
|
+
...(flashKb ? { flashKb } : {}),
|
|
985
|
+
...(hasStoragePartition ? { hasStoragePartition } : {}),
|
|
986
|
+
...(storageReg ? { storageReg } : {}),
|
|
987
|
+
counterNodes,
|
|
988
|
+
gpioControllers,
|
|
989
|
+
...(stripLed ? { stripLed } : {}),
|
|
990
|
+
...(usbDevice ? { usbDevice } : {}),
|
|
991
|
+
...(usbController ? { usbController } : {}),
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
/** First `gpios` property in a subtree (the PIO strip nests it one level down). */
|
|
995
|
+
function gpiosInSubtree(node) {
|
|
996
|
+
const own = node.props.get('gpios');
|
|
997
|
+
if (own) {
|
|
998
|
+
const ref = gpioRefFromProp(own);
|
|
999
|
+
if (ref)
|
|
1000
|
+
return ref;
|
|
1001
|
+
}
|
|
1002
|
+
for (const child of node.children.values()) {
|
|
1003
|
+
const ref = gpiosInSubtree(child);
|
|
1004
|
+
if (ref)
|
|
1005
|
+
return ref;
|
|
1006
|
+
}
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
/** All pinmux property text in a pinctrl group subtree. */
|
|
1010
|
+
function pinmuxTextIn(node, out = []) {
|
|
1011
|
+
const pm = node.props.get('pinmux');
|
|
1012
|
+
if (pm)
|
|
1013
|
+
out.push(pm);
|
|
1014
|
+
for (const child of node.children.values())
|
|
1015
|
+
pinmuxTextIn(child, out);
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function findStripLed(root, byLabel) {
|
|
1019
|
+
let found;
|
|
1020
|
+
const walk = (node) => {
|
|
1021
|
+
const compat = node.props.get('compatible') ?? '';
|
|
1022
|
+
if (compat.includes('worldsemi,ws2812') && !found) {
|
|
1023
|
+
found = stripRefOf(node, byLabel);
|
|
1024
|
+
}
|
|
1025
|
+
for (const child of node.children.values())
|
|
1026
|
+
walk(child);
|
|
1027
|
+
};
|
|
1028
|
+
walk(root);
|
|
1029
|
+
return found;
|
|
1030
|
+
}
|
|
1031
|
+
function stripRefOf(node, byLabel) {
|
|
1032
|
+
// PIO form: a descendant carries the pad as a plain gpio ref.
|
|
1033
|
+
const direct = gpiosInSubtree(node);
|
|
1034
|
+
if (direct)
|
|
1035
|
+
return direct;
|
|
1036
|
+
// SPI/I2S forms: the enclosing bus's default pinctrl group names the
|
|
1037
|
+
// data pad in its pinmux macro. Every ws2812-spi/i2s board in the tree is
|
|
1038
|
+
// ESP32-family, whose controllers are the 32-pin gpio0/gpio1 pair.
|
|
1039
|
+
const bus = node.parent;
|
|
1040
|
+
const pcLabel = bus?.props.get('pinctrl-0')?.match(/&([\w-]+)/)?.[1];
|
|
1041
|
+
const pinctrl = pcLabel ? byLabel.get(pcLabel) : undefined;
|
|
1042
|
+
if (!pinctrl)
|
|
1043
|
+
return undefined;
|
|
1044
|
+
const text = pinmuxTextIn(pinctrl).join(' ');
|
|
1045
|
+
const m = text.match(/MOSI_GPIO(\d+)|I2S\d*_O_SD_GPIO(\d+)/);
|
|
1046
|
+
const global = m ? Number(m[1] ?? m[2]) : undefined;
|
|
1047
|
+
if (global == null)
|
|
1048
|
+
return undefined;
|
|
1049
|
+
return global < 32
|
|
1050
|
+
? { controller: 'gpio0', pin: global, flags: [] }
|
|
1051
|
+
: { controller: 'gpio1', pin: global - 32, flags: [] };
|
|
1052
|
+
}
|