@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/src/toolchain/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { ToolchainOptions, CompileResult, UploadResult } from '@typecad/cut
|
|
|
26
26
|
import { parseCompileErrors } from '@typecad/cuttlefish/api/shared';
|
|
27
27
|
import { scaffoldZephyrProject, writeIfChanged, appendLibraryOverlayFragments } from './scaffold.js';
|
|
28
28
|
import { parseZephyrDts, asBuiltJson } from '../as-built.js';
|
|
29
|
+
import { stampBuildSbom } from '../sbom.js';
|
|
29
30
|
import { westSpawn, buildEnv } from './west-spawn.js';
|
|
30
31
|
import { discoverWest } from './west-discover.js';
|
|
31
32
|
import { writeDebugConfig, resolveDebugLocations, debugArtifactsNeedRewrite, DEBUG_SERVER_PORT, DEBUG_SERVER_TCL_PORT } from './debug-config.js';
|
|
@@ -39,7 +40,7 @@ import { resolveChipFromBoard } from '../chips/resolve.js';
|
|
|
39
40
|
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
40
41
|
import { pwmDtAliasToken } from '../lowering/pwm.js';
|
|
41
42
|
import { detectZephyrVersion, checkZephyrCompat, resolveBoardTarget } from './compat.js';
|
|
42
|
-
import { DEFAULT_ZEPHYR_DISPLAY_PROFILE } from '../display/profiles.js';
|
|
43
|
+
import { DEFAULT_ZEPHYR_DISPLAY_PROFILE, profileFromEmittedSource } from '../display/profiles.js';
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Resolve the chip for a build the same way the strategy does at emit time —
|
|
@@ -194,6 +195,35 @@ function scanPwmUsedPins(src: string, chip: ZephyrChipDescriptor): number[] { c
|
|
|
194
195
|
return pins;
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
export function scanMatrix(src: string): { rows: number[]; cols: number[] } | undefined {
|
|
199
|
+
// The on_key lowering's comment marker carries the construction lists.
|
|
200
|
+
const m = src.match(/cuttlefish-matrix: rows=([\d,]*) cols=([\d,]*)/);
|
|
201
|
+
if (!m) return undefined;
|
|
202
|
+
const rows = m[1] ? m[1].split(',').filter(Boolean).map(Number) : [];
|
|
203
|
+
const cols = m[2] ? m[2].split(',').filter(Boolean).map(Number) : [];
|
|
204
|
+
if (rows.length === 0 || cols.length === 0) return undefined;
|
|
205
|
+
return { rows, cols };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function scanHid(src: string): 'keyboard' | 'mouse' | undefined {
|
|
209
|
+
// The shim's report buffer names are the presence signal.
|
|
210
|
+
if (src.includes('__tc_mouse_report')) return 'mouse';
|
|
211
|
+
if (src.includes('__tc_kb_report')) return 'keyboard';
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function scanStrips(src: string): Array<{ index: number; count: number }> {
|
|
216
|
+
// The shim's buffer declaration is the presence signal:
|
|
217
|
+
// `static struct led_rgb __tc_strip<N>_buf[<COUNT>];`
|
|
218
|
+
const out = new Map<number, number>();
|
|
219
|
+
for (const m of src.matchAll(/__tc_strip(\d+)_buf\[(\d+)\]/g)) {
|
|
220
|
+
const idx = Number(m[1]);
|
|
221
|
+
const count = Number(m[2]);
|
|
222
|
+
out.set(idx, Math.max(out.get(idx) ?? 0, count));
|
|
223
|
+
}
|
|
224
|
+
return [...out.entries()].map(([index, count]) => ({ index, count }));
|
|
225
|
+
}
|
|
226
|
+
|
|
197
227
|
/**
|
|
198
228
|
* Bus controller indexes the emitted sources actually reference — the shim
|
|
199
229
|
* declares one `__tc_<bus><N>_dev` state block per used instance (gated by
|
|
@@ -226,8 +256,10 @@ function scanUsedBusInstances(
|
|
|
226
256
|
* compatible containing '_i2c<N>_' still resolves to the longest part match.
|
|
227
257
|
*/
|
|
228
258
|
export interface ScannedSensorPart {
|
|
229
|
-
part: string; busIndex: number; port: number; busKind: 'i2c' | 'spi';
|
|
259
|
+
part: string; busIndex: number; port: number; busKind: 'i2c' | 'spi' | 'w1';
|
|
230
260
|
spiHz: number; spiMode: number; alertPin: number;
|
|
261
|
+
/** 1-Wire parts: the converter resolution in bits. */
|
|
262
|
+
resolution: number;
|
|
231
263
|
}
|
|
232
264
|
|
|
233
265
|
export interface ScannedSpiTarget {
|
|
@@ -248,13 +280,22 @@ export function scanSpiTargets(src: string): ScannedSpiTarget[] {
|
|
|
248
280
|
|
|
249
281
|
export function scanSensorParts(src: string): ScannedSensorPart[] {
|
|
250
282
|
const out = new Map<string, ScannedSensorPart>();
|
|
283
|
+
// 1-Wire sensors: the stem carries the data pin (w1 master's GPIO).
|
|
284
|
+
for (const m of src.matchAll(/__tc_sensor_([a-z0-9_]+)_w1_p(\d+)_dev\b/g)) {
|
|
285
|
+
out.set(`${m[1]}|w1|${m[2]}`, { part: m[1], busIndex: 0, port: parseInt(m[2], 10), busKind: 'w1', spiHz: 0, spiMode: 0, alertPin: -1, resolution: 12 });
|
|
286
|
+
}
|
|
251
287
|
for (const m of src.matchAll(/__tc_sensor_([a-z0-9_]+)_(i2c|spi)(\d+)_(0x[0-9a-f]+|cs[0-9]+)_dev\b/g)) {
|
|
252
288
|
const port = m[4].startsWith('0x') ? parseInt(m[4], 16) : parseInt(m[4].slice(2), 10);
|
|
253
|
-
const ref: ScannedSensorPart = { part: m[1], busIndex: parseInt(m[3], 10), port, busKind: m[2] as 'i2c' | 'spi', spiHz: 0, spiMode: 0, alertPin: -1 };
|
|
289
|
+
const ref: ScannedSensorPart = { part: m[1], busIndex: parseInt(m[3], 10), port, busKind: m[2] as 'i2c' | 'spi', spiHz: 0, spiMode: 0, alertPin: -1, resolution: 12 };
|
|
254
290
|
out.set(`${ref.part}|${ref.busKind}${ref.busIndex}|${ref.port}`, ref);
|
|
255
291
|
}
|
|
256
292
|
// Construction facts ride the state block's config comment — merge by
|
|
257
293
|
// nodelabel so the scanner stays the single source for the overlay.
|
|
294
|
+
for (const m of src.matchAll(/tc-sensor-cfg: tc_([a-z0-9_]+)_w1_p(\d+) res=(\d+)/g)) {
|
|
295
|
+
const key = `${m[1]}|w1|${m[2]}`;
|
|
296
|
+
const existing = out.get(key);
|
|
297
|
+
if (existing) existing.resolution = parseInt(m[3], 10);
|
|
298
|
+
}
|
|
258
299
|
for (const m of src.matchAll(/tc-sensor-cfg: tc_([a-z0-9_]+)_(i2c|spi)(\d+)_(0x[0-9a-f]+|cs[0-9]+) hz=(\d+) mode=(\d+) alert=(-?\d+)/g)) {
|
|
259
300
|
const port = m[4].startsWith('0x') ? parseInt(m[4], 16) : parseInt(m[4].slice(2), 10);
|
|
260
301
|
const key = `${m[1]}|${m[2]}${m[3]}|${port}`;
|
|
@@ -765,11 +806,13 @@ export const Toolchain = {
|
|
|
765
806
|
const usesDisplay = uses('display_write') || uses('display_init')
|
|
766
807
|
|| uses('display_fill_rect') || uses('__tc_display_dev')
|
|
767
808
|
|| uses('CuttlefishDisplayTarget');
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
const displayProfile = usesDisplay
|
|
809
|
+
// Recover the exact display profile from the marker the adapter stamped
|
|
810
|
+
// into the emitted source (the registry is the single source of truth).
|
|
811
|
+
// Fall back to the default profile only when a display is used but no
|
|
812
|
+
// marker matched (e.g. hand-written source).
|
|
813
|
+
const displayProfile = usesDisplay
|
|
814
|
+
? (profileFromEmittedSource(src) ?? DEFAULT_ZEPHYR_DISPLAY_PROFILE)
|
|
815
|
+
: undefined;
|
|
773
816
|
// Touch controller kind comes from which DT nodelabel the emitted adapter
|
|
774
817
|
// references (FT6336U on I2C, XPT2046 on the display's SPI bus).
|
|
775
818
|
const usesTouch = uses('ft6336u') || uses('touch_');
|
|
@@ -788,12 +831,29 @@ export const Toolchain = {
|
|
|
788
831
|
spiTargets: spiTargetParts,
|
|
789
832
|
usesSpi: uses('spi_') || uses('__tc_spi') || spiTargetParts.length > 0,
|
|
790
833
|
usesUart: uses('__tc_uart'),
|
|
791
|
-
usesUsb: uses('
|
|
834
|
+
usesUsb: uses('__tc_usb0'),
|
|
792
835
|
usesPwm: uses('pwm_'),
|
|
793
836
|
usesAdc: uses('adc_'),
|
|
794
837
|
adcReadPins: scanAdcReadPins(src, chip),
|
|
795
838
|
dacWritePins: scanDacWritePins(src),
|
|
796
839
|
pwmUsedPins: scanPwmUsedPins(src, chip),
|
|
840
|
+
usesStrip: uses('led_strip'),
|
|
841
|
+
strips: scanStrips(src),
|
|
842
|
+
usesClock: uses('__tc_rtc'),
|
|
843
|
+
usesCan: uses('can_'),
|
|
844
|
+
canLoopback: uses('CAN_MODE_LOOPBACK'),
|
|
845
|
+
usesI2s: uses('i2s_'),
|
|
846
|
+
clockShimCounter: chip.hwtimer && chip.hwtimer.controllers.length > 0
|
|
847
|
+
? {
|
|
848
|
+
label: chip.hwtimer.controllers[0].nodeLabel,
|
|
849
|
+
...(chip.hwtimer.controllers[0].counterParent ? { parent: chip.hwtimer.controllers[0].counterParent } : {}),
|
|
850
|
+
}
|
|
851
|
+
: undefined,
|
|
852
|
+
usesHid: uses('__tc_hid_'),
|
|
853
|
+
hidProtocol: scanHid(src),
|
|
854
|
+
usesMatrix: uses('__tc_matrix'),
|
|
855
|
+
matrix: scanMatrix(src),
|
|
856
|
+
usesPower: uses('sys_poweroff'),
|
|
797
857
|
i2cUsedInstances: scanUsedBusInstances(src, chip.i2c?.controllers, 'i2c'),
|
|
798
858
|
spiUsedInstances: scanUsedBusInstances(src, chip.spi?.controllers, 'spi'),
|
|
799
859
|
uartUsedInstances: scanUsedBusInstances(src, chip.uart?.controllers, 'uart'),
|
|
@@ -864,7 +924,8 @@ export const Toolchain = {
|
|
|
864
924
|
const isGdbDebug = o.debug === true && debugMode === 'gdb';
|
|
865
925
|
const zc = o.zephyrConfig as Record<string, unknown> | undefined;
|
|
866
926
|
const userKconfig = zc?.kconfig as Record<string, string> | undefined;
|
|
867
|
-
const
|
|
927
|
+
const traceCfg = zc?.trace as { enabled?: boolean; intervalMs?: number } | undefined;
|
|
928
|
+
const configChanged = scaffoldZephyrProject(projectRoot, isGdbDebug, userKconfig, o.psram, traceCfg);
|
|
868
929
|
|
|
869
930
|
// Regenerate the DT overlay for the ACTUAL target board. prepare() writes
|
|
870
931
|
// it for the default board (the real target is unknown until compile), so
|
|
@@ -900,29 +961,24 @@ export const Toolchain = {
|
|
|
900
961
|
const usesDisplay = uses('display_write') || uses('display_init')
|
|
901
962
|
|| uses('display_fill_rect') || uses('__tc_display_dev')
|
|
902
963
|
|| uses('CuttlefishDisplayTarget');
|
|
903
|
-
//
|
|
904
|
-
// (
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
displayProfile = {
|
|
914
|
-
...DEFAULT_ZEPHYR_DISPLAY_PROFILE,
|
|
915
|
-
width: parseInt(wMatch[1], 10),
|
|
916
|
-
height: parseInt(hMatch[1], 10),
|
|
917
|
-
};
|
|
918
|
-
}
|
|
919
|
-
}
|
|
964
|
+
// Recover the exact display profile from the marker the adapter emitted
|
|
965
|
+
// (`typecad-display-profile: <driver>`), so the DT overlay's compatible
|
|
966
|
+
// string, geometry, and rotation match the panel the adapter targets.
|
|
967
|
+
// The width/height regex this replaces could produce franken-profiles —
|
|
968
|
+
// e.g. an ST7796S build (480x320) on the default ILI9341 profile's
|
|
969
|
+
// controller/compatible, which emitted an ilitek,ili9341 DT node for a
|
|
970
|
+
// panel the ST7796S adapter drives.
|
|
971
|
+
const displayProfile = usesDisplay
|
|
972
|
+
? (profileFromEmittedSource(src) ?? DEFAULT_ZEPHYR_DISPLAY_PROFILE)
|
|
973
|
+
: undefined;
|
|
920
974
|
// Extract display pin wiring (cs/dc/rst/spiFrequency/spiPins) from the
|
|
921
975
|
// config display section so the DT overlay wires the MIPI DBI bridge to
|
|
922
976
|
// the correct GPIOs + SPI bus pins.
|
|
923
977
|
const dispCfg = o.display as Record<string, unknown> | undefined;
|
|
924
978
|
const spiPins = (dispCfg?.spiPins ?? undefined) as
|
|
925
979
|
{ sck?: unknown; mosi?: unknown; miso?: unknown } | undefined;
|
|
980
|
+
const i2cPins = (dispCfg?.i2cPins ?? undefined) as
|
|
981
|
+
{ sda?: unknown; scl?: unknown } | undefined;
|
|
926
982
|
const wiring: DisplayWiring | undefined = dispCfg
|
|
927
983
|
? {
|
|
928
984
|
cs: typeof dispCfg.cs === 'number' ? dispCfg.cs : undefined,
|
|
@@ -932,8 +988,12 @@ export const Toolchain = {
|
|
|
932
988
|
sck: typeof spiPins?.sck === 'number' ? spiPins.sck : undefined,
|
|
933
989
|
mosi: typeof spiPins?.mosi === 'number' ? spiPins.mosi : undefined,
|
|
934
990
|
miso: typeof spiPins?.miso === 'number' ? spiPins.miso : undefined,
|
|
991
|
+
sda: typeof i2cPins?.sda === 'number' ? i2cPins.sda : undefined,
|
|
992
|
+
scl: typeof i2cPins?.scl === 'number' ? i2cPins.scl : undefined,
|
|
935
993
|
backlightPin: typeof dispCfg.backlightPin === 'number' ? dispCfg.backlightPin : undefined,
|
|
936
994
|
tearingEffectPin: typeof dispCfg.tearingEffectPin === 'number' ? dispCfg.tearingEffectPin : undefined,
|
|
995
|
+
busyPin: typeof (dispCfg as { busyPin?: number }).busyPin === 'number' ? (dispCfg as { busyPin?: number }).busyPin : undefined,
|
|
996
|
+
address: typeof dispCfg.address === 'number' ? dispCfg.address : undefined,
|
|
937
997
|
}
|
|
938
998
|
: undefined;
|
|
939
999
|
// Extract touch pin wiring from the config display.touch section so the
|
|
@@ -986,13 +1046,30 @@ export const Toolchain = {
|
|
|
986
1046
|
spiTargets: spiTargetParts,
|
|
987
1047
|
usesSpi: uses('spi_') || uses('__tc_spi') || spiTargetParts.length > 0,
|
|
988
1048
|
usesUart: uses('__tc_uart'),
|
|
989
|
-
usesUsb: uses('
|
|
1049
|
+
usesUsb: uses('__tc_usb0'),
|
|
990
1050
|
usesPwm: uses('pwm_'),
|
|
991
1051
|
usesAdc: uses('adc_'),
|
|
992
1052
|
adcReadPins: scanAdcReadPins(src, chip),
|
|
993
1053
|
usesDac: uses('dac_') || uses('__tc_dac'),
|
|
994
1054
|
dacWritePins: scanDacWritePins(src),
|
|
995
1055
|
pwmUsedPins: scanPwmUsedPins(src, chip),
|
|
1056
|
+
usesStrip: uses('led_strip'),
|
|
1057
|
+
strips: scanStrips(src),
|
|
1058
|
+
usesHid: uses('__tc_hid_'),
|
|
1059
|
+
hidProtocol: scanHid(src),
|
|
1060
|
+
usesMatrix: uses('__tc_matrix'),
|
|
1061
|
+
matrix: scanMatrix(src),
|
|
1062
|
+
usesPower: uses('sys_poweroff'),
|
|
1063
|
+
usesClock: uses('__tc_rtc'),
|
|
1064
|
+
usesCan: uses('can_'),
|
|
1065
|
+
canLoopback: uses('CAN_MODE_LOOPBACK'),
|
|
1066
|
+
usesI2s: uses('i2s_'),
|
|
1067
|
+
clockShimCounter: chip.hwtimer && chip.hwtimer.controllers.length > 0
|
|
1068
|
+
? {
|
|
1069
|
+
label: chip.hwtimer.controllers[0].nodeLabel,
|
|
1070
|
+
...(chip.hwtimer.controllers[0].counterParent ? { parent: chip.hwtimer.controllers[0].counterParent } : {}),
|
|
1071
|
+
}
|
|
1072
|
+
: undefined,
|
|
996
1073
|
i2cUsedInstances: scanUsedBusInstances(src, chip.i2c?.controllers, 'i2c'),
|
|
997
1074
|
spiUsedInstances: scanUsedBusInstances(src, chip.spi?.controllers, 'spi'),
|
|
998
1075
|
uartUsedInstances: scanUsedBusInstances(src, chip.uart?.controllers, 'uart'),
|
|
@@ -1175,6 +1252,18 @@ export const Toolchain = {
|
|
|
1175
1252
|
} catch { /* best-effort snapshot — nothing to harvest or unreadable */ }
|
|
1176
1253
|
}
|
|
1177
1254
|
|
|
1255
|
+
// SBOM stamp: after a successful build, record what the binary is made of
|
|
1256
|
+
// — kernel + linked west modules with commit SHAs + the hashed firmware
|
|
1257
|
+
// artifact (CycloneDX 1.6, next to zephyr.bin) — so `typecad-hal sbom
|
|
1258
|
+
// --check` can gate releases on the record still matching the build
|
|
1259
|
+
// (EU CRA essential-requirements support). Best-effort: a west-list or
|
|
1260
|
+
// hashing failure never blocks the build.
|
|
1261
|
+
if (result.status === 0) {
|
|
1262
|
+
try {
|
|
1263
|
+
stampBuildSbom({ buildDir, board, projectRoot });
|
|
1264
|
+
} catch { /* best-effort SBOM stamp */ }
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1178
1267
|
return {
|
|
1179
1268
|
success: result.status === 0,
|
|
1180
1269
|
output: header + output,
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
import { writeFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { resolveKconfigFragments, type KconfigUsage } from '../dt-config/kconfig.js';
|
|
13
|
-
import { scanSensorParts } from './index.js';
|
|
13
|
+
import { scanSensorParts, scanStrips, scanHid, scanMatrix } from './index.js';
|
|
14
|
+
import { profileFromEmittedSource, transportFor, panelControllerFor } from '../display/profiles.js';
|
|
15
|
+
import { readDisplayBinding } from '../display/bindings.js';
|
|
14
16
|
import { readCuttlefishLibrarySidecar } from '@typecad/cuttlefish/library-packages';
|
|
15
17
|
import { SENSOR_PART_INFO } from '@typecad/hal';
|
|
16
18
|
|
|
@@ -96,7 +98,7 @@ export function appendLibraryOverlayFragments(overlay: string, projectRoot: stri
|
|
|
96
98
|
*
|
|
97
99
|
* Idempotent. Mirrors scaffoldEspIdfProject's writeIfChanged discipline.
|
|
98
100
|
*/
|
|
99
|
-
export function scaffoldZephyrProject(projectRoot: string, debug = false, userKconfig?: Record<string, string>, psram?: 'opi' | 'quad'): boolean {
|
|
101
|
+
export function scaffoldZephyrProject(projectRoot: string, debug = false, userKconfig?: Record<string, string>, psram?: 'opi' | 'quad', trace?: { enabled?: boolean; intervalMs?: number }): boolean {
|
|
100
102
|
const srcDir = join(projectRoot, 'src');
|
|
101
103
|
if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
|
|
102
104
|
|
|
@@ -122,6 +124,22 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
122
124
|
const usage: KconfigUsage = {
|
|
123
125
|
usesAdc: uses('adc_'),
|
|
124
126
|
usesPwm: uses('pwm_'),
|
|
127
|
+
usesStrip: uses('led_strip'),
|
|
128
|
+
strips: scanStrips(src),
|
|
129
|
+
// Sensor parts drive CONFIG_W1 (1-Wire masters) and the exception
|
|
130
|
+
// lines; the same scan the exception block below re-runs.
|
|
131
|
+
sensorParts: scanSensorParts(src),
|
|
132
|
+
usesHid: uses('__tc_hid_'),
|
|
133
|
+
hidProtocol: scanHid(src),
|
|
134
|
+
usesMatrix: uses('__tc_matrix'),
|
|
135
|
+
matrix: scanMatrix(src),
|
|
136
|
+
usesPower: uses('sys_poweroff'),
|
|
137
|
+
usesClock: uses('__tc_rtc'),
|
|
138
|
+
usesCan: uses('can_'),
|
|
139
|
+
canLoopback: uses('CAN_MODE_LOOPBACK'),
|
|
140
|
+
usesI2s: uses('i2s_'),
|
|
141
|
+
// No chip facts at scaffold time — the prepare overlay omits the shim;
|
|
142
|
+
// the chip-aware build path synthesizes it.
|
|
125
143
|
usesDac: uses('dac_') || uses('__tc_dac'),
|
|
126
144
|
usesFS: uses('__tc_fs'),
|
|
127
145
|
usesHwtimer: uses('counter_') || uses('__tc_hw'),
|
|
@@ -130,6 +148,9 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
130
148
|
// call yet), but the shim still declared DEVICE_DT_GET(DT_NODELABEL(...))
|
|
131
149
|
// so the overlay must enable the node or the device symbol is missing.
|
|
132
150
|
usesI2c: uses('i2c_') || uses('__tc_i2c'),
|
|
151
|
+
// I2C target mode: every i2c.resp_* lowering calls i2c_target_register
|
|
152
|
+
// (and the shim's state carries the i2c_target_* vocabulary).
|
|
153
|
+
usesI2cTarget: uses('i2c_target_'),
|
|
133
154
|
// DT-bound sensor parts: the __tc_sensor_* state references and the
|
|
134
155
|
// sensor_sample_fetch/sensor_channel_get calls both carry the token.
|
|
135
156
|
usesSensor: uses('sensor_') || uses('__tc_sensor'),
|
|
@@ -138,12 +159,28 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
138
159
|
usesUart: uses('uart_') || uses('__tc_uart'),
|
|
139
160
|
// USB CDC serial: every usb.* lowering calls into the __tc_usb<N>_* shim
|
|
140
161
|
// (device + init helper emitted under usesUsb).
|
|
141
|
-
usesUsb: uses('
|
|
162
|
+
usesUsb: uses('__tc_usb0'),
|
|
142
163
|
// STM32F4 DBGMCU keep-SWD-alive init present (emitted for stm32f4 socs).
|
|
143
164
|
usesStm32DebugSleep: uses('__tc_stm32_dbgmcu'),
|
|
144
165
|
usesWdt: uses('wdt_'),
|
|
145
166
|
usesBle: uses('bt_') || uses('bt_gatt') || uses('bt_le_'),
|
|
146
167
|
usesDisplay: uses('display_write') || uses('display_init') || uses('display_fill_rect') || uses('__tc_display_dev') || uses('CuttlefishDisplayTarget'),
|
|
168
|
+
// Display transport + controller: recovered from the profile marker the
|
|
169
|
+
// display adapters stamp into the emitted source (the profile registry is
|
|
170
|
+
// the single source of truth — no geometry re-derivation here).
|
|
171
|
+
...((): Pick<KconfigUsage, 'displayTransport' | 'displayController' | 'displayBus' | 'displayKconfigExtra'> => {
|
|
172
|
+
const profile = profileFromEmittedSource(src);
|
|
173
|
+
if (!profile) return {};
|
|
174
|
+
// Registry profiles name a DT compatible via dtCompatible (the driver
|
|
175
|
+
// id itself isn't one); drop-in profiles use the driver string directly.
|
|
176
|
+
const bus = readDisplayBinding(profile.dtCompatible ?? profile.driver)?.busFamily;
|
|
177
|
+
if (bus === 'i2c') return { displayBus: 'i2c' };
|
|
178
|
+
// Board-provided panels (native_sim's sdl_dc) carry their own fragments
|
|
179
|
+
// and no bus at all.
|
|
180
|
+
if (profile.boardProvidesDisplay) return { displayKconfigExtra: profile.kconfig };
|
|
181
|
+
if (transportFor(profile) !== 'zephyr-display') return {};
|
|
182
|
+
return { displayTransport: 'zephyr-display', displayController: panelControllerFor(profile) };
|
|
183
|
+
})(),
|
|
147
184
|
usesTouch: uses('ft6336u') || uses('touch_'),
|
|
148
185
|
// (The power HAL is removed; its former pm_/k_sleep token scan is gone —
|
|
149
186
|
// nothing here must match tx_power_dbm's `power` substring.)
|
|
@@ -267,6 +304,28 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
267
304
|
}
|
|
268
305
|
}
|
|
269
306
|
}
|
|
307
|
+
// ── Trace heartbeat Kconfig ────────────────────────────────────────────────
|
|
308
|
+
// zephyr.trace.enabled in typecad-hal.config.ts turns on the [TR: sampler
|
|
309
|
+
// (emitted by the strategy at transpile time from the same config record).
|
|
310
|
+
// The symbol set is exactly what the sampler calls: runtime stats
|
|
311
|
+
// (k_thread_runtime_stats_*), the monitor list (k_thread_foreach —
|
|
312
|
+
// kernel/thread_monitor.c only links under THREAD_MONITOR), thread names
|
|
313
|
+
// (k_thread_name_get/set), and stack inspection (0xAA fill +
|
|
314
|
+
// stack_info.size). User zephyr.kconfig overrides still win.
|
|
315
|
+
if (trace?.enabled === true) {
|
|
316
|
+
const traceSymbols: Array<[string, string]> = [
|
|
317
|
+
['CONFIG_THREAD_RUNTIME_STATS', 'y'],
|
|
318
|
+
['CONFIG_THREAD_MONITOR', 'y'],
|
|
319
|
+
['CONFIG_THREAD_NAME', 'y'],
|
|
320
|
+
['CONFIG_INIT_STACKS', 'y'],
|
|
321
|
+
['CONFIG_THREAD_STACK_INFO', 'y'],
|
|
322
|
+
];
|
|
323
|
+
prjConf.push('', '# Trace heartbeat (typecad-hal.config.ts → zephyr.trace).');
|
|
324
|
+
for (const [sym, val] of traceSymbols) {
|
|
325
|
+
if (userKconfig && userKconfig.hasOwnProperty(sym)) continue;
|
|
326
|
+
prjConf.push(`${sym}=${val}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
270
329
|
// Per-part Kconfig exceptions: catalog parts whose driver is NOT default-y
|
|
271
330
|
// on its DT node carry extra lines here (empty today — every in-tree sensor
|
|
272
331
|
// driver lights up from the node; the path exists so the first exception
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/framework-zephyr — shared west workspace ground truth
|
|
3
|
+
//
|
|
4
|
+
// The as-built inventory both `typecad-hal licenses` and `typecad-hal sbom`
|
|
5
|
+
// report from: which west manifest modules the last build ACTUALLY compiled
|
|
6
|
+
// (linked-module filtering via the build's compile_commands.json), plus the
|
|
7
|
+
// build-directory discovery that locates it. A west manifest carries every
|
|
8
|
+
// vendor HAL/library; almost none are linked by a single project — both the
|
|
9
|
+
// license table and the SBOM scope to the linked set by default, so they stay
|
|
10
|
+
// honest about what is actually inside the firmware.
|
|
11
|
+
//
|
|
12
|
+
// Moved out of licenses.ts when the sbom command grew a second consumer; the
|
|
13
|
+
// behavior (case-insensitive, separator-agnostic path matching; degrade to
|
|
14
|
+
// "no filtering" on unparseable build data) is unchanged.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import type { ReadDir, ReadFile } from '@typecad/cuttlefish/api/shared';
|
|
19
|
+
|
|
20
|
+
/** Normalize a path for case-insensitive, separator-agnostic comparison. */
|
|
21
|
+
export function norm(p: string): string {
|
|
22
|
+
return p.split('\\').join('/').toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Filter the west manifest to the modules whose sources were compiled in the
|
|
27
|
+
* last build. A module is "linked" iff some compiled translation unit lives
|
|
28
|
+
* under its abspath. If the build data is unparseable, no filtering is applied
|
|
29
|
+
* (degrade to listing all modules rather than reporting nothing).
|
|
30
|
+
*/
|
|
31
|
+
export function filterLinkedModules(
|
|
32
|
+
ccText: string,
|
|
33
|
+
modules: { name: string; abspath: string }[],
|
|
34
|
+
): { name: string; abspath: string }[] {
|
|
35
|
+
let arr: unknown;
|
|
36
|
+
try {
|
|
37
|
+
arr = JSON.parse(ccText);
|
|
38
|
+
} catch {
|
|
39
|
+
return modules;
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(arr)) return modules;
|
|
42
|
+
// One normalized blob of every compiled source path; membership is then a
|
|
43
|
+
// substring check per module (O(modules) after an O(TUs) join).
|
|
44
|
+
const blob = arr
|
|
45
|
+
.map((e) => norm(typeof (e as { file?: unknown })?.file === 'string' ? (e as { file: string }).file : ''))
|
|
46
|
+
.join('\n');
|
|
47
|
+
return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find the build's compile_commands.json path under `cwd`. Cuttlefish's Zephyr
|
|
52
|
+
* project root is the transpile output dir (e.g. `<cwd>/src/out`), so the
|
|
53
|
+
* build dir is `<root>/build` — not necessarily `<cwd>/build`. Check the
|
|
54
|
+
* common layouts, then fall back to a bounded search (skipping
|
|
55
|
+
* node_modules/.git/dist and dot-directories). Returns the PATH (the caller
|
|
56
|
+
* decides whether to read it); undefined when no build exists.
|
|
57
|
+
*/
|
|
58
|
+
export function findCompileCommandsPath(
|
|
59
|
+
readFile: ReadFile,
|
|
60
|
+
readdir: ReadDir,
|
|
61
|
+
cwd = process.cwd(),
|
|
62
|
+
): string | undefined {
|
|
63
|
+
const direct = [
|
|
64
|
+
path.join(cwd, 'build', 'compile_commands.json'),
|
|
65
|
+
path.join(cwd, 'out', 'build', 'compile_commands.json'),
|
|
66
|
+
path.join(cwd, 'src', 'out', 'build', 'compile_commands.json'),
|
|
67
|
+
];
|
|
68
|
+
for (const c of direct) {
|
|
69
|
+
if (readFile(c)) return c;
|
|
70
|
+
}
|
|
71
|
+
// Bounded recursive search for a build/compile_commands.json.
|
|
72
|
+
const isCc = (p: string) => {
|
|
73
|
+
const n = norm(p);
|
|
74
|
+
return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
|
|
75
|
+
};
|
|
76
|
+
let found: string | undefined;
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
const walk = (dir: string, depth: number): void => {
|
|
79
|
+
if (found || depth > 4 || seen.has(dir)) return;
|
|
80
|
+
seen.add(dir);
|
|
81
|
+
let entries: string[];
|
|
82
|
+
try {
|
|
83
|
+
entries = readdir(dir);
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const e of entries) {
|
|
88
|
+
const full = path.join(dir, e);
|
|
89
|
+
if (e === 'compile_commands.json' && isCc(full)) {
|
|
90
|
+
found = full;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const e of entries) {
|
|
95
|
+
if (found) return;
|
|
96
|
+
if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.')) continue;
|
|
97
|
+
walk(path.join(dir, e), depth + 1);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
walk(cwd, 0);
|
|
101
|
+
return found;
|
|
102
|
+
}
|