@typecad/framework-zephyr 1.0.0-alpha.18 → 1.0.0-alpha.20
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 +6 -6
- 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
|
@@ -104,6 +104,91 @@ export default defineFrameworkManifest({
|
|
|
104
104
|
'pwm.set_pulse': 'supported',
|
|
105
105
|
'pwm.set_duty': 'supported',
|
|
106
106
|
'pwm.set_period': 'supported',
|
|
107
|
+
// Servo (hal/servo.ts): calibrated 50 Hz sugar on the same channel —
|
|
108
|
+
// clamping and the angle→pulse mapping run in the lowered C++.
|
|
109
|
+
'pwm.servo_us': 'supported',
|
|
110
|
+
'pwm.servo_angle': 'supported',
|
|
111
|
+
'pwm.servo_idle': 'supported',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ── Supported: LED strip (ws2812-spi over led_strip API) ───────────────
|
|
116
|
+
strip: {
|
|
117
|
+
// DAC discipline: lowers on every board with a wired SPI controller;
|
|
118
|
+
// a board with none lowers to a comment (profileDiagnostics flags it).
|
|
119
|
+
supported: true,
|
|
120
|
+
partialCoverage: true,
|
|
121
|
+
ops: {
|
|
122
|
+
// Strip (hal/strip.ts): buffer edits + one-wire-transaction show.
|
|
123
|
+
'strip.set_pixel': 'supported',
|
|
124
|
+
'strip.fill': 'supported',
|
|
125
|
+
'strip.show': 'supported',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// ── Board-gated: USB HID keyboard/mouse (zephyr,hid-device) ───────────
|
|
130
|
+
hid: {
|
|
131
|
+
// Same BOARD-GATED shape as usb below: the lowering only fires when
|
|
132
|
+
// the board's manifest carries zephyr.usb.* — the manifest probe runs
|
|
133
|
+
// with no board, so the honest declaration is unsupported here. The
|
|
134
|
+
// board module exports Keyboard/Mouse only on USB-capable boards
|
|
135
|
+
// (the module-resolution gate); one HID interface per program (the v1
|
|
136
|
+
// ceiling — mixing the two classes is a compile-time #error).
|
|
137
|
+
supported: false,
|
|
138
|
+
unsupportedReason: 'Board-gated: HID lowers only on boards whose DTS enables the USB device controller (boardgen emits zephyr.usb.* from the catalog).',
|
|
139
|
+
partialCoverage: false,
|
|
140
|
+
ops: unsupportedOps('hid.'),
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ── Supported: CAN bus (harvested can@ controllers) ────────────────────
|
|
144
|
+
can: {
|
|
145
|
+
supported: true,
|
|
146
|
+
partialCoverage: true,
|
|
147
|
+
ops: {
|
|
148
|
+
// Classic CAN (11/29-bit ids, ≤8-byte payloads). Partial: CAN-FD,
|
|
149
|
+
// bitrate data phase, and RTR frames are not surfaced in v1.
|
|
150
|
+
'can.begin': 'supported',
|
|
151
|
+
'can.send': 'supported',
|
|
152
|
+
'can.on_receive': 'supported',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// ── Supported: wall-clock time (rtc alias / counter shim) ─────────────
|
|
157
|
+
clock: {
|
|
158
|
+
supported: true,
|
|
159
|
+
partialCoverage: true,
|
|
160
|
+
ops: {
|
|
161
|
+
// set/now in Unix epoch seconds. Partial: v1 semantics are
|
|
162
|
+
// session-scoped on counter-backed boards — only hardware-calendar
|
|
163
|
+
// aliases persist across power loss.
|
|
164
|
+
'clock.set': 'supported',
|
|
165
|
+
'clock.now': 'supported',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// ── Supported: explicit power-state entry (sys_poweroff) ───────────────
|
|
170
|
+
power: {
|
|
171
|
+
supported: true,
|
|
172
|
+
partialCoverage: true,
|
|
173
|
+
ops: {
|
|
174
|
+
// Soft-off entry — never returns; wake by reset or a board wake
|
|
175
|
+
// source. Partial: the light states belong to the idle policy, not
|
|
176
|
+
// the app (no op surface for them in v1).
|
|
177
|
+
'power.off': 'supported',
|
|
178
|
+
// off_for arms the RTC wake timer before soft-off — ESP32 family
|
|
179
|
+
// (the harvested rtc_timer fact); boards without the fact lower
|
|
180
|
+
// to a comment naming the limitation (the DAC discipline).
|
|
181
|
+
'power.off_for': 'supported',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
// ── Supported: GPIO key matrix (gpio-kbd-matrix, input subsystem) ─────
|
|
186
|
+
matrix: {
|
|
187
|
+
supported: true,
|
|
188
|
+
partialCoverage: false,
|
|
189
|
+
ops: {
|
|
190
|
+
// Matrix (hal/matrix.ts): (row, col, pressed) trampoline.
|
|
191
|
+
'matrix.on_key': 'supported',
|
|
107
192
|
},
|
|
108
193
|
},
|
|
109
194
|
|
|
@@ -150,6 +235,12 @@ export default defineFrameworkManifest({
|
|
|
150
235
|
'i2c.reg_read': 'supported',
|
|
151
236
|
'i2c.reg_update': 'supported',
|
|
152
237
|
'i2c.dev_write': 'supported',
|
|
238
|
+
// I2C responder (hal/i2c-responder.ts): this board as an I2C target.
|
|
239
|
+
'i2c.resp_on_receive': 'supported',
|
|
240
|
+
'i2c.resp_on_request': 'supported',
|
|
241
|
+
'i2c.resp_available': 'supported',
|
|
242
|
+
'i2c.resp_read': 'supported',
|
|
243
|
+
'i2c.resp_write': 'supported',
|
|
153
244
|
},
|
|
154
245
|
},
|
|
155
246
|
spi: {
|
|
@@ -286,11 +377,25 @@ export default defineFrameworkManifest({
|
|
|
286
377
|
partialCoverage: true,
|
|
287
378
|
// Partial: mono profiles (ssd1306-zephyr) drive display.* ops via the
|
|
288
379
|
// direct GFX runtime only — no CuttlefishGFX UI rendering path. The
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
380
|
+
// direct-spi UI adapters (st7796-zephyr, ili9341-zephyr) drive the
|
|
381
|
+
// panel over spi_write with per-controller Adafruit init tables;
|
|
382
|
+
// st7796-zephyr is the full-fidelity path on the clone rig
|
|
383
|
+
// (18-bit mode, hardware-verified incl. colors + touch interaction;
|
|
384
|
+
// the demo needs psram:'opi' for full rendering and resetPin for the
|
|
385
|
+
// touch module's power enable). The zephyr-display transport rides
|
|
386
|
+
// in-tree panel drivers through Zephyr's display API: st7796s via an
|
|
387
|
+
// adapter-emitted CS-holding mipi-dbi host — colors hardware-confirmed
|
|
388
|
+
// correct on the rig with the byte-swap + CS-hold knobs (the stock
|
|
389
|
+
// bridge corrupts bursts on this clone; a ladder test observed slight
|
|
390
|
+
// uniform dimness vs the direct path's 18-bit mode, so direct remains
|
|
391
|
+
// the max-fidelity option there); ili9341 via the stock mipi-dbi-spi
|
|
392
|
+
// bridge is compile/link-verified only. Drop-in configs (display.driver
|
|
393
|
+
// = any bound compatible + geometry + quirk flags) ride the same path.
|
|
394
|
+
// Touch: FT6336U through the input subsystem (in-tree driver,
|
|
395
|
+
// polling mode, hardware-verified); XPT2046 stays on the raw adapter
|
|
396
|
+
// (the in-tree driver's DT scaling would double-apply calibration).
|
|
397
|
+
unsupportedReason: 'Mono panels (ssd1306) are direct-op only (no UI rendering); the direct-spi ili9341 UI path is not yet hardware-verified; the zephyr-display transport is hue-verified but 16-bit-brightness-limited on the clone st7796s (use direct-spi there) and compile-verified on ili9341; e-ink is out of scope at this time.',
|
|
398
|
+
drivers: ['ili9341-zephyr', 'ili9341-zephyr-display', 'st7796-zephyr', 'st7796-zephyr-display', 'ssd1306-zephyr'],
|
|
294
399
|
colorFormat: 'rgb565',
|
|
295
400
|
ops: {
|
|
296
401
|
'display.init': 'supported',
|
|
@@ -403,10 +508,13 @@ export default defineFrameworkManifest({
|
|
|
403
508
|
},
|
|
404
509
|
},
|
|
405
510
|
i2s: {
|
|
406
|
-
supported:
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
511
|
+
supported: true,
|
|
512
|
+
partialCoverage: true,
|
|
513
|
+
ops: {
|
|
514
|
+
'i2s.write': 'supported',
|
|
515
|
+
'i2s.read': 'supported',
|
|
516
|
+
'i2s.read_at': 'supported',
|
|
517
|
+
},
|
|
410
518
|
},
|
|
411
519
|
twai: {
|
|
412
520
|
supported: false,
|
|
@@ -533,6 +641,19 @@ export default defineFrameworkManifest({
|
|
|
533
641
|
doctor: { available: true },
|
|
534
642
|
|
|
535
643
|
// `typecad-hal licenses` enumerates the Zephyr kernel + west manifest projects
|
|
536
|
-
// and resolves each one's SPDX license
|
|
644
|
+
// and resolves each one's SPDX license.
|
|
537
645
|
licenses: { available: true },
|
|
646
|
+
|
|
647
|
+
// `typecad-hal sbom` emits a build-true SBOM (CycloneDX 1.6 / SPDX 2.3):
|
|
648
|
+
// linked west modules + kernel with commit SHAs and licenses, the hashed
|
|
649
|
+
// firmware artifact, the board, and the toolchain in `formulation`;
|
|
650
|
+
// --check/--diff gate the record against the build.
|
|
651
|
+
sbom: { available: true },
|
|
652
|
+
|
|
653
|
+
// `typecad-hal audit` evaluates the last build's merged Kconfig against a
|
|
654
|
+
// security baseline (shell/console exposure, entropy, watchdog, hard-coded
|
|
655
|
+
// credentials...), with committed waivers (.typecad-hal/audit-waivers.json)
|
|
656
|
+
// recorded as deviations and a machine-readable sidecar
|
|
657
|
+
// (<buildDir>/security-audit.json).
|
|
658
|
+
audit: { available: true },
|
|
538
659
|
});
|
package/src/index.ts
CHANGED
|
@@ -26,9 +26,19 @@ export { runDoctor as doctor } from './doctor.js';
|
|
|
26
26
|
// `typecad-hal licenses` — enumerate the Zephyr kernel + west manifest projects
|
|
27
27
|
// and resolve each one's SPDX license. Re-exported under the dispatcher-facing
|
|
28
28
|
// alias `licenses` so the loader picks it up as mod.licenses (see
|
|
29
|
-
// framework-package.ts).
|
|
29
|
+
// framework-package.ts).
|
|
30
30
|
export { runLicensesPresenter as licenses } from './licenses.js';
|
|
31
31
|
|
|
32
|
+
// `typecad-hal sbom` — emit/verify a build-true software bill of materials
|
|
33
|
+
// (CycloneDX 1.6 / SPDX 2.3). Re-exported under the dispatcher-facing alias
|
|
34
|
+
// `sbom` so the loader picks it up as mod.sbom (see framework-package.ts).
|
|
35
|
+
export { runSbomPresenter as sbom } from './sbom.js';
|
|
36
|
+
|
|
37
|
+
// `typecad-hal audit` — evaluate the last build's merged Kconfig against the
|
|
38
|
+
// security baseline, with committed waivers recorded as deviations.
|
|
39
|
+
// Re-exported under the dispatcher-facing alias `audit` (same loader pattern).
|
|
40
|
+
export { runAuditPresenter as audit } from './audit.js';
|
|
41
|
+
|
|
32
42
|
// Chip descriptor cache (the lowering's resolved-board view; no curated
|
|
33
43
|
// registry — every board reconstructs from its generated manifest).
|
|
34
44
|
export {
|
package/src/licenses.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from '@typecad/cuttlefish/api/shared';
|
|
38
38
|
import { discoverWest } from './toolchain/west-discover.js';
|
|
39
39
|
import { westSpawn } from './toolchain/west-spawn.js';
|
|
40
|
+
import { filterLinkedModules, findCompileCommandsPath } from './west-inventory.js';
|
|
40
41
|
|
|
41
42
|
// ---------------------------------------------------------------------------
|
|
42
43
|
// Outcome + runner types
|
|
@@ -72,40 +73,6 @@ export interface ZephyrLicensesRunner {
|
|
|
72
73
|
readdir: ReadDir;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// Linked-module filtering (project scope)
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
/** Normalize a path for case-insensitive, separator-agnostic comparison. */
|
|
80
|
-
function norm(p: string): string {
|
|
81
|
-
return p.split('\\').join('/').toLowerCase();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Filter the west manifest to the modules whose sources were compiled in the
|
|
86
|
-
* last build. A module is "linked" iff some compiled translation unit lives
|
|
87
|
-
* under its abspath. If the build data is unparseable, no filtering is applied
|
|
88
|
-
* (degrade to listing all modules rather than reporting nothing).
|
|
89
|
-
*/
|
|
90
|
-
function filterLinkedModules(
|
|
91
|
-
ccText: string,
|
|
92
|
-
modules: { name: string; abspath: string }[],
|
|
93
|
-
): { name: string; abspath: string }[] {
|
|
94
|
-
let arr: unknown;
|
|
95
|
-
try {
|
|
96
|
-
arr = JSON.parse(ccText);
|
|
97
|
-
} catch {
|
|
98
|
-
return modules;
|
|
99
|
-
}
|
|
100
|
-
if (!Array.isArray(arr)) return modules;
|
|
101
|
-
// One normalized blob of every compiled source path; membership is then a
|
|
102
|
-
// substring check per module (O(modules) after an O(TUs) join).
|
|
103
|
-
const blob = arr
|
|
104
|
-
.map((e) => norm(typeof (e as { file?: unknown })?.file === 'string' ? (e as { file: string }).file : ''))
|
|
105
|
-
.join('\n');
|
|
106
|
-
return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
76
|
// ---------------------------------------------------------------------------
|
|
110
77
|
// scanZephyrLicenses — enumerate + resolve the workspace dependency set
|
|
111
78
|
// ---------------------------------------------------------------------------
|
|
@@ -205,55 +172,6 @@ export function scanZephyrLicenses(
|
|
|
205
172
|
// Default runner — wires seams to discoverWest + westSpawn + fs
|
|
206
173
|
// ---------------------------------------------------------------------------
|
|
207
174
|
|
|
208
|
-
/**
|
|
209
|
-
* Find the build's compile_commands.json under cwd. Cuttlefish's Zephyr project
|
|
210
|
-
* root is the transpile output dir (e.g. `<cwd>/src/out`), so the build dir is
|
|
211
|
-
* `<root>/build` — not necessarily `<cwd>/build`. Check the common layouts,
|
|
212
|
-
* then fall back to a bounded search (skipping node_modules/.git/dist).
|
|
213
|
-
*/
|
|
214
|
-
function findCompileCommandsText(readFile: ReadFile, readdir: ReadDir): string | null {
|
|
215
|
-
const direct = [
|
|
216
|
-
path.join(process.cwd(), 'build', 'compile_commands.json'),
|
|
217
|
-
path.join(process.cwd(), 'out', 'build', 'compile_commands.json'),
|
|
218
|
-
path.join(process.cwd(), 'src', 'out', 'build', 'compile_commands.json'),
|
|
219
|
-
];
|
|
220
|
-
for (const c of direct) {
|
|
221
|
-
const text = readFile(c);
|
|
222
|
-
if (text) return text;
|
|
223
|
-
}
|
|
224
|
-
// Bounded recursive search for a build/compile_commands.json.
|
|
225
|
-
const isCc = (p: string) => {
|
|
226
|
-
const n = norm(p);
|
|
227
|
-
return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
|
|
228
|
-
};
|
|
229
|
-
let found: string | undefined;
|
|
230
|
-
const seen = new Set<string>();
|
|
231
|
-
const walk = (dir: string, depth: number): void => {
|
|
232
|
-
if (found || depth > 4 || seen.has(dir)) return;
|
|
233
|
-
seen.add(dir);
|
|
234
|
-
let entries: string[];
|
|
235
|
-
try {
|
|
236
|
-
entries = readdir(dir);
|
|
237
|
-
} catch {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
for (const e of entries) {
|
|
241
|
-
const full = path.join(dir, e);
|
|
242
|
-
if (e === 'compile_commands.json' && isCc(full)) {
|
|
243
|
-
found = full;
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
for (const e of entries) {
|
|
248
|
-
if (found) return;
|
|
249
|
-
if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.')) continue;
|
|
250
|
-
walk(path.join(dir, e), depth + 1);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
walk(process.cwd(), 0);
|
|
254
|
-
return found ? (readFile(found) ?? null) : null;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
175
|
function defaultRunner(): ZephyrLicensesRunner | null {
|
|
258
176
|
const install = discoverWest();
|
|
259
177
|
if (!install) return null;
|
|
@@ -315,7 +233,10 @@ function defaultRunner(): ZephyrLicensesRunner | null {
|
|
|
315
233
|
topdir,
|
|
316
234
|
listModules,
|
|
317
235
|
zephyrBase,
|
|
318
|
-
compileCommands: () =>
|
|
236
|
+
compileCommands: () => {
|
|
237
|
+
const p = findCompileCommandsPath(readFile, readdir);
|
|
238
|
+
return p ? (readFile(p) ?? null) : null;
|
|
239
|
+
},
|
|
319
240
|
readFile,
|
|
320
241
|
readdir,
|
|
321
242
|
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// CAN lowering — Zephyr's can API over the harvested controller node
|
|
3
|
+
//
|
|
4
|
+
// The controller is addressed by its DT nodelabel (chip.can, the harvested
|
|
5
|
+
// can@ node — ESP32 TWAI, STM32 bxCAN, NXP FlexCAN…). begin() applies
|
|
6
|
+
// mode + bitrate + start in the required order (mode and bitrate need a
|
|
7
|
+
// stopped controller); send() builds one can_frame and submits it with a
|
|
8
|
+
// bounded timeout and no tx callback (blocking-send form); onReceive()
|
|
9
|
+
// installs the accept-all filter whose trampoline carries the frame as
|
|
10
|
+
// ten scalars — the Matrix discipline (ISR context, set-a-variable work).
|
|
11
|
+
// ----------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
14
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
15
|
+
|
|
16
|
+
/** The C variable names for a CAN controller's state. */
|
|
17
|
+
function prefix(idx: number): string {
|
|
18
|
+
return `__tc_can${idx}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Which can.* verbs the program actually uses on a controller — the
|
|
22
|
+
* init block emits only the state those verbs reference (Zephyr's -Werror
|
|
23
|
+
* turns an unreferenced static function/variable into a build failure:
|
|
24
|
+
* a send-only program must not carry the rx trampoline). */
|
|
25
|
+
export interface CanUsage {
|
|
26
|
+
begin: boolean;
|
|
27
|
+
send: boolean;
|
|
28
|
+
onReceive: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Emit the per-controller CAN state: device handle, rx trampoline +
|
|
33
|
+
* handler slot, and the accept-all filters. Called from shimLines when the
|
|
34
|
+
* program uses can.* on this controller and the chip declares it.
|
|
35
|
+
*/
|
|
36
|
+
export function canInitLines(chip: ZephyrChipDescriptor, controllerIndex: number, usage: CanUsage): string[] {
|
|
37
|
+
const ctrl = chip.can?.controllers[controllerIndex];
|
|
38
|
+
if (!ctrl) return [];
|
|
39
|
+
const p = prefix(controllerIndex);
|
|
40
|
+
const lines: string[] = [
|
|
41
|
+
'// CUTTLEFISH_CAN_BEGIN',
|
|
42
|
+
`static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
|
|
43
|
+
];
|
|
44
|
+
if (usage.begin) {
|
|
45
|
+
// Shim-scoped (not per call site): a second begin() anywhere in the
|
|
46
|
+
// program must not re-fire can_start on a started controller.
|
|
47
|
+
lines.push(`static bool ${p}_started = false;`);
|
|
48
|
+
}
|
|
49
|
+
if (usage.onReceive) {
|
|
50
|
+
lines.push(
|
|
51
|
+
`static void (*${p}_rx)(double, double, double, double, double, double, double, double, double, double) = NULL;`,
|
|
52
|
+
// dlc-bounded: drivers fill only data[0..dlc-1] — copy into a zeroed
|
|
53
|
+
// local so the handler's b0..b7 tail is zero, as the class documents.
|
|
54
|
+
'static void __tc_can_rx_trampoline(const struct device* dev, struct can_frame* frame, void* user_data) {',
|
|
55
|
+
' (void)dev;',
|
|
56
|
+
' (void)user_data;',
|
|
57
|
+
` if (${p}_rx != NULL) {`,
|
|
58
|
+
' uint8_t __tc_d[8] = { 0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U };',
|
|
59
|
+
' for (uint32_t __tc_i = 0U; (__tc_i < 8U) && (__tc_i < static_cast<uint32_t>(frame->dlc)); ++__tc_i) { __tc_d[__tc_i] = frame->data[__tc_i]; }',
|
|
60
|
+
` ${p}_rx(static_cast<double>(frame->id), static_cast<double>(frame->dlc),`,
|
|
61
|
+
` static_cast<double>(__tc_d[0]), static_cast<double>(__tc_d[1]),`,
|
|
62
|
+
` static_cast<double>(__tc_d[2]), static_cast<double>(__tc_d[3]),`,
|
|
63
|
+
` static_cast<double>(__tc_d[4]), static_cast<double>(__tc_d[5]),`,
|
|
64
|
+
` static_cast<double>(__tc_d[6]), static_cast<double>(__tc_d[7]));`,
|
|
65
|
+
' }',
|
|
66
|
+
'}',
|
|
67
|
+
// Two accept-all filters: can_filter_matches_filter never delivers an
|
|
68
|
+
// IDE (29-bit) frame to a filter without CAN_FILTER_IDE — one filter
|
|
69
|
+
// per frame format, so extended sends are receivable too.
|
|
70
|
+
`static const struct can_filter ${p}_filter = { .id = 0U, .mask = 0U, .flags = 0U };`,
|
|
71
|
+
`static const struct can_filter ${p}_filter_ext = { .id = 0U, .mask = 0U, .flags = CAN_FILTER_IDE };`,
|
|
72
|
+
`static bool ${p}_filter_added = false;`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (usage.send) {
|
|
76
|
+
lines.push(
|
|
77
|
+
`static void __tc_can_tx_done(const struct device* dev, int error, void* user_data) {`,
|
|
78
|
+
' (void)dev; (void)error; (void)user_data;',
|
|
79
|
+
'}',
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
lines.push('// CUTTLEFISH_CAN_END');
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a HAL can.* op to Zephyr C++.
|
|
88
|
+
* Returns `{ code }` for statement ops.
|
|
89
|
+
*/
|
|
90
|
+
export function lowerCan(
|
|
91
|
+
op: HALOpIR,
|
|
92
|
+
chip: ZephyrChipDescriptor,
|
|
93
|
+
): { code?: string; expression?: string } {
|
|
94
|
+
const o = op as any;
|
|
95
|
+
// DAC discipline: a board with no harvested can@ node lowers to a comment
|
|
96
|
+
// naming the constraint (the board gate already refuses the CAN import;
|
|
97
|
+
// this keeps the manifest probe and direct-lowering paths honest).
|
|
98
|
+
if (!chip.can || chip.can.controllers.length === 0) {
|
|
99
|
+
return { code: `/* ${op.operation}: this board's chip data declares no CAN controller (no harvested can@ node) */` };
|
|
100
|
+
}
|
|
101
|
+
const idx = Math.min(Number(o.instance ?? 0), chip.can.controllers.length - 1);
|
|
102
|
+
const p = prefix(idx);
|
|
103
|
+
const started = `${p}_started`;
|
|
104
|
+
|
|
105
|
+
switch (op.operation) {
|
|
106
|
+
case 'can.begin': {
|
|
107
|
+
// Mode and bitrate require a STOPPED controller — apply them before
|
|
108
|
+
// the start, and start exactly once. Failures printk (the loud-
|
|
109
|
+
// diagnostics policy): a CAN bus that never comes up is otherwise a
|
|
110
|
+
// silent hang at the first send.
|
|
111
|
+
return {
|
|
112
|
+
code: `{ int __tc_err; if (${o.loopback}) { __tc_err = can_set_mode(${p}_dev, CAN_MODE_LOOPBACK); if (__tc_err != 0) { printk("typecad-hal can: set_mode failed: %d\\n", __tc_err); } } __tc_err = can_set_bitrate(${p}_dev, ${o.hz}); if (__tc_err != 0) { printk("typecad-hal can: bitrate failed: %d\\n", __tc_err); } if (!${started}) { ${started} = true; __tc_err = can_start(${p}_dev); if (__tc_err != 0) { printk("typecad-hal can: start failed: %d\\n", __tc_err); } } }`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
case 'can.send': {
|
|
116
|
+
const bytes = (o.data ?? []) as (number | string)[];
|
|
117
|
+
if (bytes.length > 8) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`CAN send: classic frames carry at most 8 bytes — got ${bytes.length}.`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const assigns = bytes
|
|
123
|
+
.map((b, i) => `__tc_frame.data[${i}] = static_cast<uint8_t>(${b});`)
|
|
124
|
+
.join(' ');
|
|
125
|
+
return {
|
|
126
|
+
code: `{ struct can_frame __tc_frame = { 0 }; __tc_frame.id = static_cast<uint32_t>(${o.id}); __tc_frame.dlc = ${bytes.length}U; __tc_frame.flags = ${o.extended ? 'CAN_FRAME_IDE' : '0U'}; ${assigns} int __tc_err = can_send(${p}_dev, &__tc_frame, K_MSEC(100), __tc_can_tx_done, NULL); if (__tc_err != 0) { printk("typecad-hal can: send failed: %d\\n", __tc_err); } }`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
case 'can.on_receive': {
|
|
130
|
+
return {
|
|
131
|
+
code: `{ ${p}_rx = ${o.handler}; if (!${p}_filter_added) { ${p}_filter_added = true; (void)can_add_rx_filter(${p}_dev, __tc_can_rx_trampoline, NULL, &${p}_filter); (void)can_add_rx_filter(${p}_dev, __tc_can_rx_trampoline, NULL, &${p}_filter_ext); } }`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
throw new Error(
|
|
136
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
137
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Clock lowering — rtc_set_time / rtc_get_time on the `rtc` DT alias
|
|
3
|
+
//
|
|
4
|
+
// The alias is Zephyr's own discovery convention. Boards with a hardware
|
|
5
|
+
// calendar RTC alias it themselves; the overlay generator synthesizes a
|
|
6
|
+
// zephyr,rtc-counter shim (a child of the board's first free counter — the
|
|
7
|
+
// counter driver keeps the parent, so Counter and Clock coexist) when they
|
|
8
|
+
// do not.
|
|
9
|
+
//
|
|
10
|
+
// Epoch ↔ civil conversion is Howard Hinnant's days_from_civil pair —
|
|
11
|
+
// pure int64 arithmetic, no libc timezone machinery, valid over the whole
|
|
12
|
+
// Unix range. The helpers live in the shim block so both ops share them.
|
|
13
|
+
// ----------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
16
|
+
|
|
17
|
+
const RTC_DEV = 'DEVICE_DT_GET(DT_ALIAS(rtc))';
|
|
18
|
+
|
|
19
|
+
/** The conversion helpers + device handle. Called from shimLines when the
|
|
20
|
+
* program uses clock.* — each helper only when its consuming op appears
|
|
21
|
+
* (a now()-only program must not carry the unused set() helper: -Werror
|
|
22
|
+
* fails the build on unreferenced static functions). */
|
|
23
|
+
export function clockInitLines(usage: { set: boolean; now: boolean }): string[] {
|
|
24
|
+
const lines: string[] = [
|
|
25
|
+
'// CUTTLEFISH_CLOCK_BEGIN',
|
|
26
|
+
`static const struct device* __tc_rtc = ${RTC_DEV};`,
|
|
27
|
+
];
|
|
28
|
+
if (usage.now) {
|
|
29
|
+
lines.push(
|
|
30
|
+
'static int64_t __tc_days_from_civil(int32_t yy, int32_t mm, int32_t dd) {',
|
|
31
|
+
' yy -= (mm <= 2) ? 1 : 0;',
|
|
32
|
+
' const int32_t era = (yy >= 0 ? yy : yy - 399) / 400;',
|
|
33
|
+
' const int32_t yoe = yy - era * 400;',
|
|
34
|
+
' const int32_t doy = (153 * (mm + (mm > 2 ? -3 : 9)) + 2) / 5 + dd - 1;',
|
|
35
|
+
' const int32_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;',
|
|
36
|
+
' return (static_cast<int64_t>(era) * 146097) + static_cast<int64_t>(doe) - 719468;',
|
|
37
|
+
'}',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (usage.set) {
|
|
41
|
+
lines.push(
|
|
42
|
+
'static void __tc_civil_from_days(int64_t z, int32_t* yy, int32_t* mm, int32_t* dd) {',
|
|
43
|
+
' z += 719468;',
|
|
44
|
+
' const int64_t era = (z >= 0 ? z : z - 146096) / 146097;',
|
|
45
|
+
' const int64_t doe = z - (era * 146097);',
|
|
46
|
+
' const int64_t yoe = (doe - (doe / 1460) + (doe / 36524) - (doe / 146096)) / 365;',
|
|
47
|
+
' const int64_t y = yoe + (era * 400);',
|
|
48
|
+
' const int64_t doy = doe - ((365 * yoe) + (yoe / 4) - (yoe / 100));',
|
|
49
|
+
' const int64_t mp = ((5 * doy) + 2) / 153;',
|
|
50
|
+
' const int64_t d = doy - (((153 * mp) + 2) / 5) + 1;',
|
|
51
|
+
' const int64_t m = mp + (mp < 10 ? 3 : -9);',
|
|
52
|
+
' *yy = static_cast<int32_t>(y + (m <= 2 ? 1 : 0));',
|
|
53
|
+
' *mm = static_cast<int32_t>(m);',
|
|
54
|
+
' *dd = static_cast<int32_t>(d);',
|
|
55
|
+
'}',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
lines.push('// CUTTLEFISH_CLOCK_END');
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The all-zero/unknown rtc_time initializer (wday/yday unknown per the
|
|
63
|
+
* struct's contract). */
|
|
64
|
+
const TM_INIT = 'struct rtc_time __tc_tm = { 0, 0, 0, 0, 0, 0, -1, -1, -1, 0 }';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a HAL clock.* op to Zephyr C++.
|
|
68
|
+
* Returns `{ code }` for set, `{ expression }` for now.
|
|
69
|
+
*/
|
|
70
|
+
export function lowerClock(op: HALOpIR): { code?: string; expression?: string } {
|
|
71
|
+
const o = op as any;
|
|
72
|
+
|
|
73
|
+
switch (op.operation) {
|
|
74
|
+
case 'clock.set': {
|
|
75
|
+
// epoch (runtime text) → civil date → rtc_set_time.
|
|
76
|
+
return {
|
|
77
|
+
code: `{ int64_t __tc_ep = static_cast<int64_t>(${o.epoch}); int32_t __tc_y = 0; int32_t __tc_mo = 0; int32_t __tc_d = 0; __tc_civil_from_days(__tc_ep / 86400, &__tc_y, &__tc_mo, &__tc_d); int64_t __tc_sod = __tc_ep % 86400; ${TM_INIT}; __tc_tm.tm_year = __tc_y - 1900; __tc_tm.tm_mon = __tc_mo - 1; __tc_tm.tm_mday = __tc_d; __tc_tm.tm_hour = static_cast<int32_t>(__tc_sod / 3600); __tc_tm.tm_min = static_cast<int32_t>((__tc_sod % 3600) / 60); __tc_tm.tm_sec = static_cast<int32_t>(__tc_sod % 60); (void)rtc_set_time(__tc_rtc, &__tc_tm); }`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case 'clock.now': {
|
|
81
|
+
return {
|
|
82
|
+
expression: `({ ${TM_INIT}; double __tc_epoch = 0.0; if (rtc_get_time(__tc_rtc, &__tc_tm) == 0) { int64_t __tc_days = __tc_days_from_civil(__tc_tm.tm_year + 1900, __tc_tm.tm_mon + 1, __tc_tm.tm_mday); __tc_epoch = static_cast<double>((__tc_days * 86400) + (static_cast<int64_t>(__tc_tm.tm_hour) * 3600) + (static_cast<int64_t>(__tc_tm.tm_min) * 60) + static_cast<int64_t>(__tc_tm.tm_sec)); } __tc_epoch; })`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
throw new Error(
|
|
87
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
88
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|