@typecad/framework-zephyr 1.0.0-alpha.18 → 1.0.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.js +63 -5
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +21 -0
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +118 -48
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +53 -101
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +123 -26
- package/dist/dt-config/overlay.d.ts +13 -0
- package/dist/dt-config/overlay.js +431 -16
- package/dist/framework.manifest.d.ts +9 -3
- package/dist/framework.manifest.js +123 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/licenses.js +5 -84
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +140 -0
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/power.d.ts +10 -0
- package/dist/lowering/power.js +35 -0
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +6 -3
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/strategy.d.ts +60 -2
- package/dist/strategy.js +485 -25
- package/dist/toolchain/index.d.ts +12 -1
- package/dist/toolchain/index.js +115 -27
- package/dist/toolchain/scaffold.d.ts +4 -1
- package/dist/toolchain/scaffold.js +67 -3
- package/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- package/package.json +3 -3
- package/src/audit.ts +529 -0
- package/src/boardgen.ts +54 -5
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +576 -567
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +274 -204
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +51 -100
- package/src/dt-config/kconfig.ts +647 -511
- package/src/dt-config/overlay.ts +433 -16
- package/src/framework.manifest.ts +131 -10
- package/src/index.ts +11 -1
- package/src/licenses.ts +5 -84
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/i2c.ts +146 -0
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +224 -221
- package/src/sbom.ts +1117 -0
- package/src/strategy.ts +440 -25
- package/src/toolchain/index.ts +117 -28
- package/src/toolchain/scaffold.ts +62 -3
- package/src/west-inventory.ts +102 -0
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ReadFile } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
export type KconfigValue = string | number | boolean;
|
|
3
|
+
/** Parse a merged Kconfig file: `CONFIG_X=y` (true), `# CONFIG_X is not set`
|
|
4
|
+
* (explicit false), `CONFIG_X=123` / `CONFIG_X="s"` (value). Comment blocks
|
|
5
|
+
* and everything else are ignored. Absent symbols are simply not in the map
|
|
6
|
+
* (Kconfig default applies — treat as "not enabled" for y/n symbols). */
|
|
7
|
+
export declare function parseMergedConfig(text: string): Map<string, KconfigValue>;
|
|
8
|
+
/** Sweep a resolved devicetree for every `compatible` string — the enabled
|
|
9
|
+
* hardware the firmware actually wired (attack-surface inventory input).
|
|
10
|
+
* Multi-string compatibles ("a,b", "a,b-fallback") contribute EVERY entry
|
|
11
|
+
* (the fallback binds the same node); nodes marked status = "disabled"
|
|
12
|
+
* contribute nothing. Node scoping is a brace-depth walk: every block is a
|
|
13
|
+
* node, and only its OWN properties (nested child blocks stripped) decide
|
|
14
|
+
* its status/compatibles. */
|
|
15
|
+
export declare function sweepCompatibles(dtsText: string): string[];
|
|
16
|
+
export interface SecurityFacts {
|
|
17
|
+
config: Map<string, KconfigValue>;
|
|
18
|
+
compatibles: string[];
|
|
19
|
+
}
|
|
20
|
+
export type AuditSeverity = 'high' | 'medium' | 'low';
|
|
21
|
+
export interface AuditFinding {
|
|
22
|
+
rule: string;
|
|
23
|
+
title: string;
|
|
24
|
+
severity: AuditSeverity;
|
|
25
|
+
requirement: string;
|
|
26
|
+
remediation: string;
|
|
27
|
+
/** The as-built evidence line(s) that triggered the rule. */
|
|
28
|
+
evidence: string;
|
|
29
|
+
}
|
|
30
|
+
export interface AuditRule {
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
severity: AuditSeverity;
|
|
34
|
+
/** CRA essential-requirement paraphrase this rule maps to. */
|
|
35
|
+
requirement: string;
|
|
36
|
+
remediation: string;
|
|
37
|
+
/** Returns the evidence string when the rule fires, else null. */
|
|
38
|
+
evaluate: (facts: SecurityFacts) => string | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The Zephyr security baseline. Rules read ONLY config/devicetree facts —
|
|
42
|
+
* never a board name or SoC family (all-boards-are-equal). Every rule must
|
|
43
|
+
* be waivable with a recorded justification: real products have real
|
|
44
|
+
* exceptions, and a conformity assessor wants to see them documented, not
|
|
45
|
+
* hidden.
|
|
46
|
+
*/
|
|
47
|
+
export declare const SECURITY_RULES: readonly AuditRule[];
|
|
48
|
+
/** Evaluate the baseline over the facts. Order follows rule severity. */
|
|
49
|
+
export declare function evaluateSecurityRules(facts: SecurityFacts): AuditFinding[];
|
|
50
|
+
export interface AuditWaiver {
|
|
51
|
+
rule: string;
|
|
52
|
+
justification: string;
|
|
53
|
+
date?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function parseWaiverFile(text: string | undefined): {
|
|
56
|
+
waivers: AuditWaiver[];
|
|
57
|
+
error?: string;
|
|
58
|
+
};
|
|
59
|
+
/** Find .typecad-hal/audit-waivers.json, walking up from `cwd` (bounded). */
|
|
60
|
+
export declare function findWaiverFile(readFile: ReadFile, cwd: string): string | undefined;
|
|
61
|
+
export interface AuditRunner {
|
|
62
|
+
cwd?: string;
|
|
63
|
+
/** The last build's compile_commands.json path, or undefined. */
|
|
64
|
+
compileCommandsPath?: () => string | undefined;
|
|
65
|
+
readFile: ReadFile;
|
|
66
|
+
}
|
|
67
|
+
export interface AuditReport {
|
|
68
|
+
schema: 'typecad-hal/security-audit@1';
|
|
69
|
+
generated: string;
|
|
70
|
+
buildDir: string;
|
|
71
|
+
summary: {
|
|
72
|
+
console: 'uart' | 'usb' | 'rtt' | 'none';
|
|
73
|
+
shell: boolean;
|
|
74
|
+
network: boolean;
|
|
75
|
+
bluetooth: boolean;
|
|
76
|
+
usbDevice: boolean;
|
|
77
|
+
entropy: boolean;
|
|
78
|
+
watchdog: boolean;
|
|
79
|
+
hardware: string[];
|
|
80
|
+
};
|
|
81
|
+
findings: AuditFinding[];
|
|
82
|
+
deviations: (AuditFinding & {
|
|
83
|
+
justification: string;
|
|
84
|
+
date?: string;
|
|
85
|
+
})[];
|
|
86
|
+
rulesEvaluated: number;
|
|
87
|
+
waiverFile?: string;
|
|
88
|
+
}
|
|
89
|
+
export declare function buildAuditReport(args: {
|
|
90
|
+
buildDir: string;
|
|
91
|
+
facts: SecurityFacts;
|
|
92
|
+
findings: AuditFinding[];
|
|
93
|
+
deviations: (AuditFinding & {
|
|
94
|
+
justification: string;
|
|
95
|
+
date?: string;
|
|
96
|
+
})[];
|
|
97
|
+
generated?: string;
|
|
98
|
+
waiverFile?: string;
|
|
99
|
+
}): AuditReport;
|
|
100
|
+
/** Options accepted by `runAuditPresenter` (structurally compatible with the
|
|
101
|
+
* parsed `typecad-hal audit` CLI options). */
|
|
102
|
+
export interface AuditPresenterOptions {
|
|
103
|
+
/** Exit 1 on any unwaived high/medium finding. */
|
|
104
|
+
strict?: boolean;
|
|
105
|
+
/** Print only the JSON report (clean CI pipe); the sidecar is still written. */
|
|
106
|
+
json?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/** @internal Test-only override of the default runner. */
|
|
109
|
+
export declare function __setAuditRunnerForTest(runner: AuditRunner | undefined): void;
|
|
110
|
+
/** `typecad-hal audit` presenter. */
|
|
111
|
+
export declare function runAuditPresenter(options: AuditPresenterOptions): void;
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/framework-zephyr — security baseline audit
|
|
3
|
+
//
|
|
4
|
+
// `typecad-hal audit` — evaluate the last build's MERGED Kconfig
|
|
5
|
+
// (<buildDir>/zephyr/.config, the as-built truth nothing else reads) against
|
|
6
|
+
// a curated Zephyr security baseline, in the spirit of the EU Cyber
|
|
7
|
+
// Resilience Act's essential requirements (secure by default, attack surface
|
|
8
|
+
// minimised, no hard-coded credentials, functioning crypto underpinnings).
|
|
9
|
+
//
|
|
10
|
+
// Architecture mirrors the AUTOSAR compliance module and the sbom command:
|
|
11
|
+
// rules are DATA evaluated over facts, never board-name branches; justified
|
|
12
|
+
// exceptions are DEVIATIONS recorded in a committed waiver file
|
|
13
|
+
// (.typecad-hal/audit-waivers.json) rather than suppressed findings; the
|
|
14
|
+
// presenter never calls process.exit() and sets process.exitCode under
|
|
15
|
+
// --strict only for unwaived high/medium findings. A machine-readable
|
|
16
|
+
// sidecar (<buildDir>/security-audit.json) is written on every run — the
|
|
17
|
+
// input the CRA technical file and the future VEX/audit feed consume.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
import { readFileSync, readdirSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import * as ui from '@typecad/cuttlefish/utils/ui';
|
|
22
|
+
import { loadTypecadConfig } from '@typecad/cuttlefish/config-loader';
|
|
23
|
+
import { findCompileCommandsPath } from './west-inventory.js';
|
|
24
|
+
/** Parse a merged Kconfig file: `CONFIG_X=y` (true), `# CONFIG_X is not set`
|
|
25
|
+
* (explicit false), `CONFIG_X=123` / `CONFIG_X="s"` (value). Comment blocks
|
|
26
|
+
* and everything else are ignored. Absent symbols are simply not in the map
|
|
27
|
+
* (Kconfig default applies — treat as "not enabled" for y/n symbols). */
|
|
28
|
+
export function parseMergedConfig(text) {
|
|
29
|
+
const out = new Map();
|
|
30
|
+
for (const line of text.split(/\r?\n/)) {
|
|
31
|
+
const notSet = line.match(/^#\s*CONFIG_([A-Z0-9_]+) is not set$/);
|
|
32
|
+
if (notSet) {
|
|
33
|
+
out.set(`CONFIG_${notSet[1]}`, false);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const set = line.match(/^CONFIG_([A-Z0-9_]+)=(.*)$/);
|
|
37
|
+
if (set) {
|
|
38
|
+
const raw = set[2].trim();
|
|
39
|
+
if (raw === 'y')
|
|
40
|
+
out.set(`CONFIG_${set[1]}`, true);
|
|
41
|
+
else if (raw === 'n')
|
|
42
|
+
out.set(`CONFIG_${set[1]}`, false);
|
|
43
|
+
else if (/^-?\d+$/.test(raw))
|
|
44
|
+
out.set(`CONFIG_${set[1]}`, Number(raw));
|
|
45
|
+
else if (/^".*"$/s.test(raw))
|
|
46
|
+
out.set(`CONFIG_${set[1]}`, raw.slice(1, -1));
|
|
47
|
+
else
|
|
48
|
+
out.set(`CONFIG_${set[1]}`, raw);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Sweep a resolved devicetree for every `compatible` string — the enabled
|
|
54
|
+
* hardware the firmware actually wired (attack-surface inventory input).
|
|
55
|
+
* Multi-string compatibles ("a,b", "a,b-fallback") contribute EVERY entry
|
|
56
|
+
* (the fallback binds the same node); nodes marked status = "disabled"
|
|
57
|
+
* contribute nothing. Node scoping is a brace-depth walk: every block is a
|
|
58
|
+
* node, and only its OWN properties (nested child blocks stripped) decide
|
|
59
|
+
* its status/compatibles. */
|
|
60
|
+
export function sweepCompatibles(dtsText) {
|
|
61
|
+
const found = new Set();
|
|
62
|
+
// The block's own properties: the text with nested child blocks removed.
|
|
63
|
+
const ownProps = (body) => {
|
|
64
|
+
let out = '';
|
|
65
|
+
let depth = 0;
|
|
66
|
+
for (const ch of body) {
|
|
67
|
+
if (ch === '{') {
|
|
68
|
+
depth++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (ch === '}') {
|
|
72
|
+
depth--;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (depth === 0)
|
|
76
|
+
out += ch;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
// Visit EVERY node block (advancing past '{' only, so nested nodes are
|
|
81
|
+
// found by later iterations too).
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < dtsText.length) {
|
|
84
|
+
const open = dtsText.indexOf('{', i);
|
|
85
|
+
if (open < 0)
|
|
86
|
+
break;
|
|
87
|
+
let depth = 1;
|
|
88
|
+
let j = open + 1;
|
|
89
|
+
while (j < dtsText.length && depth > 0) {
|
|
90
|
+
const c = dtsText[j];
|
|
91
|
+
if (c === '{')
|
|
92
|
+
depth++;
|
|
93
|
+
else if (c === '}')
|
|
94
|
+
depth--;
|
|
95
|
+
j++;
|
|
96
|
+
}
|
|
97
|
+
const props = ownProps(dtsText.slice(open + 1, Math.min(j, dtsText.length) - (depth === 0 ? 1 : 0)));
|
|
98
|
+
if (!/status\s*=\s*"disabled"/.test(props)) {
|
|
99
|
+
for (const m of props.matchAll(/compatible\s*=\s*((?:"[^"]*"\s*,?\s*)+)/g)) {
|
|
100
|
+
for (const s of m[1].matchAll(/"([^"]+)"/g))
|
|
101
|
+
found.add(s[1]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
i = open + 1;
|
|
105
|
+
}
|
|
106
|
+
return [...found].sort();
|
|
107
|
+
}
|
|
108
|
+
const on = (f, name) => f.config.get(name) === true;
|
|
109
|
+
const anyOn = (f, ...names) => names.some((n) => on(f, n));
|
|
110
|
+
/**
|
|
111
|
+
* The Zephyr security baseline. Rules read ONLY config/devicetree facts —
|
|
112
|
+
* never a board name or SoC family (all-boards-are-equal). Every rule must
|
|
113
|
+
* be waivable with a recorded justification: real products have real
|
|
114
|
+
* exceptions, and a conformity assessor wants to see them documented, not
|
|
115
|
+
* hidden.
|
|
116
|
+
*/
|
|
117
|
+
export const SECURITY_RULES = [
|
|
118
|
+
{
|
|
119
|
+
id: 'zephyr.gdbstub',
|
|
120
|
+
title: 'remote debug stub compiled into the image',
|
|
121
|
+
severity: 'high',
|
|
122
|
+
requirement: 'Annex I 1.2(a) — attack surface minimised; no remotely exploitable debug interfaces',
|
|
123
|
+
remediation: 'disable CONFIG_GDBSTUB for production builds (gate it behind a debug build type)',
|
|
124
|
+
evaluate: (f) => (on(f, 'CONFIG_GDBSTUB') ? 'CONFIG_GDBSTUB=y' : null),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'zephyr.bt-fixed-passkey',
|
|
128
|
+
title: 'static Bluetooth pairing passkey (hard-coded credential)',
|
|
129
|
+
severity: 'high',
|
|
130
|
+
requirement: 'Annex I 1.2(f) — no hard-coded credentials; unique per-device secrets',
|
|
131
|
+
remediation: 'remove CONFIG_BT_FIXED_PASSKEY and use per-device pairing (just-in-time/numeric comparison)',
|
|
132
|
+
evaluate: (f) => {
|
|
133
|
+
const v = f.config.get('CONFIG_BT_FIXED_PASSKEY');
|
|
134
|
+
return typeof v === 'number' ? `CONFIG_BT_FIXED_PASSKEY=${v}` : null;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'zephyr.entropy-missing',
|
|
139
|
+
title: 'crypto/networking enabled without a hardware entropy source',
|
|
140
|
+
severity: 'high',
|
|
141
|
+
requirement: 'Annex I 1.2(c) — cryptographic primitives backed by proper randomness',
|
|
142
|
+
remediation: 'enable CONFIG_ENTROPY_GENERATOR (the board SoC entropy driver) — keys and nonces '
|
|
143
|
+
+ 'derived without it are predictable',
|
|
144
|
+
evaluate: (f) => {
|
|
145
|
+
if (on(f, 'CONFIG_ENTROPY_GENERATOR'))
|
|
146
|
+
return null;
|
|
147
|
+
const consumers = ['CONFIG_BT', 'CONFIG_NETWORKING', 'CONFIG_MBEDTLS', 'CONFIG_TINYCRYPT'].filter((n) => on(f, n));
|
|
148
|
+
if (consumers.length === 0)
|
|
149
|
+
return null;
|
|
150
|
+
return '# CONFIG_ENTROPY_GENERATOR is not set (while '
|
|
151
|
+
+ consumers.map((c) => `${c}=y`).join(', ')
|
|
152
|
+
+ ')';
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'zephyr.shell-enabled',
|
|
157
|
+
title: 'interactive shell compiled in',
|
|
158
|
+
severity: 'high',
|
|
159
|
+
requirement: 'Annex I 1.2(a) — attack surface minimised; no unnecessary interactive interfaces',
|
|
160
|
+
remediation: 'disable CONFIG_SHELL for production builds, or restrict it to a debug build type',
|
|
161
|
+
evaluate: (f) => (on(f, 'CONFIG_SHELL') ? 'CONFIG_SHELL=y' : null),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'zephyr.network-no-tls',
|
|
165
|
+
title: 'network stack enabled without a TLS library',
|
|
166
|
+
severity: 'medium',
|
|
167
|
+
requirement: 'Annex I 1.2(c)/(e) — confidential communication over networks',
|
|
168
|
+
remediation: 'enable CONFIG_MBEDTLS (mbedTLS SSL) for networked firmware, or document why the '
|
|
169
|
+
+ 'traffic may stay in cleartext',
|
|
170
|
+
evaluate: (f) => on(f, 'CONFIG_NETWORKING') && !on(f, 'CONFIG_MBEDTLS')
|
|
171
|
+
? 'CONFIG_NETWORKING=y, # CONFIG_MBEDTLS is not set'
|
|
172
|
+
: null,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'zephyr.watchdog-absent',
|
|
176
|
+
title: 'no watchdog configured',
|
|
177
|
+
severity: 'medium',
|
|
178
|
+
requirement: 'Annex I 1.3(b) — resilience and availability of essential functions',
|
|
179
|
+
remediation: 'enable CONFIG_WATCHDOG and feed it from the app (hal: `new Watchdog(...)` on a '
|
|
180
|
+
+ 'watchdog-capable board) so hangs recover instead of bricking the product',
|
|
181
|
+
evaluate: (f) => (on(f, 'CONFIG_WATCHDOG') ? null : '# CONFIG_WATCHDOG is not set'),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'zephyr.console-enabled',
|
|
185
|
+
title: 'diagnostic console enabled',
|
|
186
|
+
severity: 'low',
|
|
187
|
+
requirement: 'Annex I 1.2(a) — attack surface minimised (informational: consoles are often '
|
|
188
|
+
+ 'intentional in the field)',
|
|
189
|
+
remediation: 'if the console is not needed in production, disable it (CONFIG_UART_CONSOLE / '
|
|
190
|
+
+ 'CONFIG_USB_CONSOLE / CONFIG_RTT_CONSOLE)',
|
|
191
|
+
evaluate: (f) => {
|
|
192
|
+
const which = ['CONFIG_UART_CONSOLE', 'CONFIG_USB_CONSOLE', 'CONFIG_RTT_CONSOLE'].filter((n) => on(f, n));
|
|
193
|
+
return which.length > 0 ? which.join(', ') + ' enabled' : null;
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'zephyr.hardening-off',
|
|
198
|
+
title: 'compiler hardening stack not enabled',
|
|
199
|
+
severity: 'low',
|
|
200
|
+
requirement: 'Annex I 1.2(b) — reduce exploitability of residual vulnerabilities',
|
|
201
|
+
remediation: 'enable CONFIG_HARDENING (stack canaries and friends) unless the overhead is '
|
|
202
|
+
+ 'measured to matter',
|
|
203
|
+
evaluate: (f) => (on(f, 'CONFIG_HARDENING') ? null : 'CONFIG_HARDENING not enabled'),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
/** Evaluate the baseline over the facts. Order follows rule severity. */
|
|
207
|
+
export function evaluateSecurityRules(facts) {
|
|
208
|
+
const order = { high: 0, medium: 1, low: 2 };
|
|
209
|
+
const findings = [];
|
|
210
|
+
for (const rule of SECURITY_RULES) {
|
|
211
|
+
const evidence = rule.evaluate(facts);
|
|
212
|
+
if (evidence !== null) {
|
|
213
|
+
findings.push({
|
|
214
|
+
rule: rule.id,
|
|
215
|
+
title: rule.title,
|
|
216
|
+
severity: rule.severity,
|
|
217
|
+
requirement: rule.requirement,
|
|
218
|
+
remediation: rule.remediation,
|
|
219
|
+
evidence,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
findings.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
224
|
+
return findings;
|
|
225
|
+
}
|
|
226
|
+
export function parseWaiverFile(text) {
|
|
227
|
+
if (!text)
|
|
228
|
+
return { waivers: [] };
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(text);
|
|
231
|
+
if (!parsed || !Array.isArray(parsed.waivers)) {
|
|
232
|
+
return { waivers: [], error: 'waiver file must be `{ "waivers": [...] }` — ignored' };
|
|
233
|
+
}
|
|
234
|
+
const waivers = [];
|
|
235
|
+
for (const w of parsed.waivers) {
|
|
236
|
+
if (w &&
|
|
237
|
+
typeof w === 'object' &&
|
|
238
|
+
typeof w.rule === 'string' &&
|
|
239
|
+
typeof w.justification === 'string') {
|
|
240
|
+
const entry = w;
|
|
241
|
+
if (entry.justification.trim().length === 0) {
|
|
242
|
+
return { waivers: [], error: `waiver for ${entry.rule} has an empty justification — ignored` };
|
|
243
|
+
}
|
|
244
|
+
waivers.push(entry);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { waivers };
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return { waivers: [], error: 'waiver file is not valid JSON — ignored' };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Find .typecad-hal/audit-waivers.json, walking up from `cwd` (bounded). */
|
|
254
|
+
export function findWaiverFile(readFile, cwd) {
|
|
255
|
+
let dir = path.resolve(cwd);
|
|
256
|
+
for (let i = 0; i < 5; i++) {
|
|
257
|
+
const p = path.join(dir, '.typecad-hal', 'audit-waivers.json');
|
|
258
|
+
if (readFile(p))
|
|
259
|
+
return p;
|
|
260
|
+
const parent = path.dirname(dir);
|
|
261
|
+
if (parent === dir)
|
|
262
|
+
break;
|
|
263
|
+
dir = parent;
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
function defaultAuditRunner(cwd = process.cwd()) {
|
|
268
|
+
const readFile = (p) => {
|
|
269
|
+
try {
|
|
270
|
+
return readFileSync(p, 'utf8');
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const readdir = (d) => {
|
|
277
|
+
try {
|
|
278
|
+
return readdirSync(d);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
cwd,
|
|
286
|
+
compileCommandsPath: () => findCompileCommandsPath(readFile, readdir, cwd),
|
|
287
|
+
readFile,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
export function buildAuditReport(args) {
|
|
291
|
+
const f = args.facts;
|
|
292
|
+
const consoleType = on(f, 'CONFIG_UART_CONSOLE')
|
|
293
|
+
? 'uart'
|
|
294
|
+
: on(f, 'CONFIG_USB_CONSOLE')
|
|
295
|
+
? 'usb'
|
|
296
|
+
: on(f, 'CONFIG_RTT_CONSOLE')
|
|
297
|
+
? 'rtt'
|
|
298
|
+
: 'none';
|
|
299
|
+
return {
|
|
300
|
+
schema: 'typecad-hal/security-audit@1',
|
|
301
|
+
generated: args.generated ?? new Date().toISOString(),
|
|
302
|
+
buildDir: args.buildDir,
|
|
303
|
+
summary: {
|
|
304
|
+
console: consoleType,
|
|
305
|
+
shell: on(f, 'CONFIG_SHELL'),
|
|
306
|
+
network: on(f, 'CONFIG_NETWORKING'),
|
|
307
|
+
bluetooth: on(f, 'CONFIG_BT'),
|
|
308
|
+
usbDevice: anyOn(f, 'CONFIG_USB_DEVICE_STACK', 'CONFIG_USB_DEVICE_DRIVER'),
|
|
309
|
+
entropy: on(f, 'CONFIG_ENTROPY_GENERATOR'),
|
|
310
|
+
watchdog: on(f, 'CONFIG_WATCHDOG'),
|
|
311
|
+
hardware: f.compatibles,
|
|
312
|
+
},
|
|
313
|
+
findings: args.findings,
|
|
314
|
+
deviations: args.deviations,
|
|
315
|
+
rulesEvaluated: SECURITY_RULES.length,
|
|
316
|
+
waiverFile: args.waiverFile,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
let testRunner;
|
|
320
|
+
/** @internal Test-only override of the default runner. */
|
|
321
|
+
export function __setAuditRunnerForTest(runner) {
|
|
322
|
+
testRunner = runner;
|
|
323
|
+
}
|
|
324
|
+
function printFinding(f, waived) {
|
|
325
|
+
// ui.printWarning already stamps its own "! " marker.
|
|
326
|
+
ui.printWarning(`${waived ? 'waived ' : ''}[${f.severity}] ${f.rule} — ${f.title}`);
|
|
327
|
+
ui.printInfo(` requirement: ${f.requirement}`);
|
|
328
|
+
ui.printInfo(` evidence: ${f.evidence}`);
|
|
329
|
+
ui.printInfo(` fix: ${f.remediation}`);
|
|
330
|
+
if (waived)
|
|
331
|
+
ui.printInfo(` waived: ${waived}`);
|
|
332
|
+
}
|
|
333
|
+
/** `typecad-hal audit` presenter. */
|
|
334
|
+
export function runAuditPresenter(options) {
|
|
335
|
+
const runner = testRunner ?? defaultAuditRunner();
|
|
336
|
+
const cwd = runner.cwd ?? process.cwd();
|
|
337
|
+
const ccPath = runner.compileCommandsPath ? runner.compileCommandsPath() : undefined;
|
|
338
|
+
const buildDir = ccPath ? path.dirname(ccPath) : undefined;
|
|
339
|
+
const configText = buildDir ? runner.readFile(path.join(buildDir, 'zephyr', '.config')) : undefined;
|
|
340
|
+
if (!buildDir || configText === undefined) {
|
|
341
|
+
ui.printError('No build found — audit reads the merged zephyr/.config of the last build. Run typecad-hal build first.');
|
|
342
|
+
process.exitCode = 1;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const dtsText = runner.readFile(path.join(buildDir, 'zephyr', 'zephyr.dts'));
|
|
346
|
+
const facts = {
|
|
347
|
+
config: parseMergedConfig(configText),
|
|
348
|
+
compatibles: dtsText ? sweepCompatibles(dtsText) : [],
|
|
349
|
+
};
|
|
350
|
+
const all = evaluateSecurityRules(facts);
|
|
351
|
+
const waiverPath = findWaiverFile(runner.readFile, cwd);
|
|
352
|
+
const { waivers, error: waiverError } = parseWaiverFile(waiverPath ? runner.readFile(waiverPath) : undefined);
|
|
353
|
+
const waivedByRule = new Map(waivers.map((w) => [w.rule, w]));
|
|
354
|
+
const findings = all.filter((f) => !waivedByRule.has(f.rule));
|
|
355
|
+
const deviations = all
|
|
356
|
+
.filter((f) => waivedByRule.has(f.rule))
|
|
357
|
+
.map((f) => ({ ...f, ...waivedByRule.get(f.rule) }));
|
|
358
|
+
const report = buildAuditReport({ buildDir, facts, findings, deviations, waiverFile: waiverPath });
|
|
359
|
+
// Sidecar first (best-effort, never fatal), then the human/JSON surface.
|
|
360
|
+
try {
|
|
361
|
+
mkdirSync(buildDir, { recursive: true });
|
|
362
|
+
writeFileSync(path.join(buildDir, 'security-audit.json'), JSON.stringify(report, null, 2) + '\n');
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
/* best-effort sidecar */
|
|
366
|
+
}
|
|
367
|
+
// --strict: unwaived high/medium findings fail. Waived findings are
|
|
368
|
+
// recorded deviations, not failures.
|
|
369
|
+
const failing = findings.filter((f) => f.severity === 'high' || f.severity === 'medium');
|
|
370
|
+
if (options.strict && failing.length > 0) {
|
|
371
|
+
process.exitCode = 1;
|
|
372
|
+
}
|
|
373
|
+
if (options.json) {
|
|
374
|
+
console.log(JSON.stringify(report, null, 2));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
ui.printHeader();
|
|
378
|
+
ui.printStep('Auditing the merged build configuration (security baseline)');
|
|
379
|
+
let boardTarget;
|
|
380
|
+
try {
|
|
381
|
+
boardTarget = loadTypecadConfig(process.cwd())?.buildTarget;
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
/* best-effort */
|
|
385
|
+
}
|
|
386
|
+
if (boardTarget)
|
|
387
|
+
ui.printInfo(`board ............. ${boardTarget}`);
|
|
388
|
+
const s = report.summary;
|
|
389
|
+
ui.printInfo(`surface ........... console ${s.console} · shell ${s.shell ? 'ON' : 'off'} · net ${s.network ? 'ON' : 'off'} · ` +
|
|
390
|
+
`BLE ${s.bluetooth ? 'ON' : 'off'} · USB device ${s.usbDevice ? 'ON' : 'off'}`);
|
|
391
|
+
ui.printInfo(`underpinnings ..... entropy ${s.entropy ? 'ok' : 'OFF'} · watchdog ${s.watchdog ? 'ok' : 'OFF'} · ` +
|
|
392
|
+
`${s.hardware.length} wired compatible(s)`);
|
|
393
|
+
if (waiverError)
|
|
394
|
+
ui.printWarning(`waiver file: ${waiverError}`);
|
|
395
|
+
for (const f of findings)
|
|
396
|
+
printFinding(f);
|
|
397
|
+
for (const d of deviations) {
|
|
398
|
+
printFinding(d, `${d.justification}${d.date ? ` (${d.date})` : ''}`);
|
|
399
|
+
}
|
|
400
|
+
if (all.length === 0) {
|
|
401
|
+
ui.printSuccess(`baseline clean — ${report.rulesEvaluated} rules evaluated, 0 findings.`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
ui.printWarning(`${findings.length} finding(s) (${failing.length} unwaived high/medium), ` +
|
|
405
|
+
`${deviations.length} waived deviation(s).`);
|
|
406
|
+
}
|
|
407
|
+
if (waiverPath) {
|
|
408
|
+
ui.printInfo(`waivers .......... ${waiverPath}`);
|
|
409
|
+
}
|
|
410
|
+
ui.printInfo(`sidecar .......... ${path.join(buildDir, 'security-audit.json')}`);
|
|
411
|
+
if (options.strict) {
|
|
412
|
+
ui.printInfo(failing.length > 0
|
|
413
|
+
? '--strict .......... FAIL (unwaived high/medium findings above)'
|
|
414
|
+
: '--strict .......... pass');
|
|
415
|
+
}
|
|
416
|
+
}
|
package/dist/boardgen.js
CHANGED
|
@@ -947,21 +947,43 @@ export function buildModule(entry, userFacts, factsSuffix = '', seedWarnings = [
|
|
|
947
947
|
if (wdtNodeLabel)
|
|
948
948
|
ts.push(`export { Watchdog } from '@typecad/hal/core';`);
|
|
949
949
|
if (siliconPwm.length > 0 || pwmLedSpecs.length > 0 || pwmMatrix)
|
|
950
|
-
ts.push(`export { PWM } from '@typecad/hal/core';`);
|
|
950
|
+
ts.push(`export { PWM, Servo } from '@typecad/hal/core';`);
|
|
951
951
|
if (siliconAdc.length > 0)
|
|
952
952
|
ts.push(`export { ADC } from '@typecad/hal/core';`);
|
|
953
953
|
if (siliconDac.length > 0)
|
|
954
954
|
ts.push(`export { DAC } from '@typecad/hal/core';`);
|
|
955
|
+
// The responder rides the same wired-bus fact as I2CTarget — target-mode
|
|
956
|
+
// support is a controller-driver property discovered at registration
|
|
957
|
+
// (-ENOSYS prints loudly), never a board-name gate.
|
|
955
958
|
if (buses.i2c.length > 0)
|
|
956
|
-
ts.push(`export { I2CTarget } from '@typecad/hal/core';`);
|
|
959
|
+
ts.push(`export { I2CTarget, I2CResponder } from '@typecad/hal/core';`);
|
|
957
960
|
if (buses.spi.length > 0)
|
|
958
961
|
ts.push(`export { SPITarget } from '@typecad/hal/core';`);
|
|
959
962
|
if (buses.uart.length > 0)
|
|
960
963
|
ts.push(`export { UART } from '@typecad/hal/core';`);
|
|
961
964
|
if (hwtimerControllers.length > 0)
|
|
962
965
|
ts.push(`export { Counter } from '@typecad/hal/core';`);
|
|
966
|
+
// Clock rides the same free-counter facts (the zephyr,rtc-counter shim
|
|
967
|
+
// wraps the counter when the board ships no hardware rtc alias).
|
|
968
|
+
if (hwtimerControllers.length > 0)
|
|
969
|
+
ts.push(`export { Clock } from '@typecad/hal/core';`);
|
|
970
|
+
// CAN rides the harvested controller nodes (ESP32 TWAI, STM32 bxCAN,
|
|
971
|
+
// NXP FlexCAN…); a SoC with none exports nothing.
|
|
972
|
+
if ((entry.canNodes ?? []).length > 0)
|
|
973
|
+
ts.push(`export { CAN } from '@typecad/hal/core';`);
|
|
974
|
+
// I2S rides the harvested i2s@ controllers.
|
|
975
|
+
if ((entry.i2sNodes ?? []).length > 0)
|
|
976
|
+
ts.push(`export { I2S } from '@typecad/hal/core';`);
|
|
977
|
+
// Power rides the SoC's declared cpu-power-states (both the ESP32 family's
|
|
978
|
+
// standby + soft-off pair and STM32's suspend-to-idle count; a SoC with no
|
|
979
|
+
// declared states exports nothing).
|
|
980
|
+
if ((entry.powerStates ?? []).length > 0)
|
|
981
|
+
ts.push(`export { Power } from '@typecad/hal/core';`);
|
|
982
|
+
// KEY/MOUSE are ungated token tables — the derived always-available
|
|
983
|
+
// re-export line above already carries them on every board; only the HID
|
|
984
|
+
// device classes ride the USB gate.
|
|
963
985
|
if (hasUsb)
|
|
964
|
-
ts.push(`export { USBConsole } from '@typecad/hal/core';`);
|
|
986
|
+
ts.push(`export { USBConsole, Keyboard, Mouse } from '@typecad/hal/core';`);
|
|
965
987
|
// Store/File: a persisted backend needs a storage region — either the
|
|
966
988
|
// board's own storage_partition (harvested reg) or a synthesizable one
|
|
967
989
|
// (flash size known, no existing partition to collide with).
|
|
@@ -1077,12 +1099,15 @@ export function buildModule(entry, userFacts, factsSuffix = '', seedWarnings = [
|
|
|
1077
1099
|
// GPIO driver API (`gpio_pin_configure` flags + `gpio_pin_interrupt_configure`):
|
|
1078
1100
|
// every GPIO controller implements them, so they are universally true —
|
|
1079
1101
|
// a platform fact, not a per-pad one. Analog/PWM ride the harvested
|
|
1080
|
-
// silicon routes (honest per-pad facts)
|
|
1102
|
+
// silicon routes (honest per-pad facts); PWM counts both route kinds —
|
|
1103
|
+
// fixed pinctrl routes AND any-pad matrices (ESP32 LEDC, nRF psel), the
|
|
1104
|
+
// same set union the pin JSDoc and the PWM export gate read, so the
|
|
1105
|
+
// hover annotation and the capability flag cannot disagree.
|
|
1081
1106
|
constants[`pins.all.${i}.capabilities.digitalInput`] = true;
|
|
1082
1107
|
constants[`pins.all.${i}.capabilities.digitalOutput`] = true;
|
|
1083
1108
|
constants[`pins.all.${i}.capabilities.analogInput`] = siliconAdcPins.has(p.halPin);
|
|
1084
1109
|
constants[`pins.all.${i}.capabilities.analogOutput`] = siliconDacPins.has(p.halPin);
|
|
1085
|
-
constants[`pins.all.${i}.capabilities.pwm`] = siliconPwmPins.has(p.halPin);
|
|
1110
|
+
constants[`pins.all.${i}.capabilities.pwm`] = siliconPwmPins.has(p.halPin) || (matrixPwmPins?.has(p.halPin) ?? false);
|
|
1086
1111
|
constants[`pins.all.${i}.capabilities.interrupt`] = true;
|
|
1087
1112
|
constants[`pins.all.${i}.capabilities.pullUp`] = true;
|
|
1088
1113
|
constants[`pins.all.${i}.capabilities.pullDown`] = true;
|
|
@@ -1091,6 +1116,39 @@ export function buildModule(entry, userFacts, factsSuffix = '', seedWarnings = [
|
|
|
1091
1116
|
constants[`pins.all.${i}.capabilities.touch`] = false;
|
|
1092
1117
|
constants[`pins.all.${i}.capabilities.openDrain`] = true;
|
|
1093
1118
|
});
|
|
1119
|
+
// CPU power states — the harvested PM capability facts (which levels
|
|
1120
|
+
// exist, their idle timing, and whether the automatic policy may enter
|
|
1121
|
+
// them; ESP32's soft-off is explicit-entry only).
|
|
1122
|
+
const powerStates = entry.powerStates ?? [];
|
|
1123
|
+
powerStates.forEach((ps, i) => {
|
|
1124
|
+
constants[`zephyr.power.states.${i}.name`] = ps.name;
|
|
1125
|
+
if (ps.minResidencyUs !== undefined)
|
|
1126
|
+
constants[`zephyr.power.states.${i}.minResidencyUs`] = ps.minResidencyUs;
|
|
1127
|
+
if (ps.exitLatencyUs !== undefined)
|
|
1128
|
+
constants[`zephyr.power.states.${i}.exitLatencyUs`] = ps.exitLatencyUs;
|
|
1129
|
+
constants[`zephyr.power.states.${i}.enabled`] = ps.enabled;
|
|
1130
|
+
});
|
|
1131
|
+
if (powerStates.length > 0) {
|
|
1132
|
+
constants['zephyr.power.softOff'] = powerStates.some((ps) => ps.name === 'soft-off');
|
|
1133
|
+
}
|
|
1134
|
+
if (entry.rtcWakeTimer)
|
|
1135
|
+
constants['zephyr.power.wakeTimer'] = true;
|
|
1136
|
+
// CAN controllers — the harvested labeled nodes (the CAN class gate). The
|
|
1137
|
+
// ESP32 TWAI pads ride along: loopback overlays route both TWAI functions
|
|
1138
|
+
// onto the TX pad.
|
|
1139
|
+
(entry.canNodes ?? []).forEach((c, i) => {
|
|
1140
|
+
constants[`zephyr.can.controllers.${i}.nodeLabel`] = c.nodeLabel;
|
|
1141
|
+
constants[`zephyr.can.controllers.${i}.compatible`] = c.compatible;
|
|
1142
|
+
if (c.txPad !== undefined)
|
|
1143
|
+
constants[`zephyr.can.controllers.${i}.txPad`] = c.txPad;
|
|
1144
|
+
if (c.rxPad !== undefined)
|
|
1145
|
+
constants[`zephyr.can.controllers.${i}.rxPad`] = c.rxPad;
|
|
1146
|
+
});
|
|
1147
|
+
// I2S controllers — the harvested labeled nodes (the I2S class gate).
|
|
1148
|
+
(entry.i2sNodes ?? []).forEach((c, i) => {
|
|
1149
|
+
constants[`zephyr.i2s.controllers.${i}.nodeLabel`] = c.nodeLabel;
|
|
1150
|
+
constants[`zephyr.i2s.controllers.${i}.compatible`] = c.compatible;
|
|
1151
|
+
});
|
|
1094
1152
|
constants['peripherals.i2c.count'] = buses.i2c.length;
|
|
1095
1153
|
constants['peripherals.spi.count'] = buses.spi.length;
|
|
1096
1154
|
constants['peripherals.uart.count'] = buses.uart.length;
|
package/dist/chips/resolve.js
CHANGED
|
@@ -196,6 +196,23 @@ export function resolveChipFromBoard(bc) {
|
|
|
196
196
|
// (+ optional vid/pid for the device descriptor).
|
|
197
197
|
const usbController = bc.get('zephyr.usb.controller');
|
|
198
198
|
const usbCdcInstances = bc.get('zephyr.usb.cdcInstances');
|
|
199
|
+
const wakeTimer = bc.get('zephyr.power.wakeTimer');
|
|
200
|
+
const i2sControllers = [];
|
|
201
|
+
for (let ii = 0; bc.get(`zephyr.i2s.controllers.${ii}.nodeLabel`) !== undefined; ii++) {
|
|
202
|
+
i2sControllers.push({
|
|
203
|
+
nodeLabel: String(bc.get(`zephyr.i2s.controllers.${ii}.nodeLabel`)),
|
|
204
|
+
compatible: String(bc.get(`zephyr.i2s.controllers.${ii}.compatible`)),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const canControllers = [];
|
|
208
|
+
for (let ci = 0; bc.get(`zephyr.can.controllers.${ci}.nodeLabel`) !== undefined; ci++) {
|
|
209
|
+
canControllers.push({
|
|
210
|
+
nodeLabel: String(bc.get(`zephyr.can.controllers.${ci}.nodeLabel`)),
|
|
211
|
+
compatible: String(bc.get(`zephyr.can.controllers.${ci}.compatible`)),
|
|
212
|
+
...(bc.get(`zephyr.can.controllers.${ci}.txPad`) !== undefined ? { txPad: Number(bc.get(`zephyr.can.controllers.${ci}.txPad`)) } : {}),
|
|
213
|
+
...(bc.get(`zephyr.can.controllers.${ci}.rxPad`) !== undefined ? { rxPad: Number(bc.get(`zephyr.can.controllers.${ci}.rxPad`)) } : {}),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
199
216
|
const usbVid = bc.get('zephyr.usb.vid');
|
|
200
217
|
const usbPid = bc.get('zephyr.usb.pid');
|
|
201
218
|
// Optional 1200-baud touch-to-reset data (BOSSA-bootloader boards).
|
|
@@ -327,6 +344,9 @@ export function resolveChipFromBoard(bc) {
|
|
|
327
344
|
...(storageOffset != null && storageSize != null
|
|
328
345
|
? { storage: { offset: storageOffset, size: storageSize, ...(storagePreexisting ? { preexisting: true } : {}) } }
|
|
329
346
|
: {}),
|
|
347
|
+
...(wakeTimer ? { powerWakeTimer: true } : {}),
|
|
348
|
+
...(canControllers.length > 0 ? { can: { controllers: canControllers } } : {}),
|
|
349
|
+
...(i2sControllers.length > 0 ? { i2s: { controllers: i2sControllers } } : {}),
|
|
330
350
|
...(usbController && usbCdcInstances && usbCdcInstances > 0
|
|
331
351
|
? {
|
|
332
352
|
usb: {
|