diff --git a/Cargo.lock b/Cargo.lock index abd4b26a9f..b0faa32295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "byteorder", "phash", "serde", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -178,8 +178,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1007,8 +1007,8 @@ dependencies = [ "sha3", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1026,8 +1026,8 @@ dependencies = [ "stm32h7", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1069,8 +1069,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1097,8 +1097,8 @@ dependencies = [ "ringbuf", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1108,8 +1108,8 @@ dependencies = [ "counters", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1123,8 +1123,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1139,8 +1139,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1156,8 +1156,8 @@ dependencies = [ "sha3", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1174,7 +1174,7 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -1197,8 +1197,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1208,8 +1208,8 @@ dependencies = [ "drv-fpga-api", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1228,8 +1228,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1266,8 +1266,8 @@ dependencies = [ "static_assertions", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1290,8 +1290,8 @@ dependencies = [ "task-jefe-api", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1304,8 +1304,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1323,8 +1323,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1334,8 +1334,8 @@ dependencies = [ "counters", "drv-i2c-types", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1353,8 +1353,8 @@ dependencies = [ "smbus-pec", "task-power-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1399,8 +1399,8 @@ dependencies = [ "serde", "static_assertions", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1418,8 +1418,8 @@ dependencies = [ "mutable-statics", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1437,8 +1437,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1451,8 +1451,8 @@ dependencies = [ "drv-oxide-vpd", "idol", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1475,8 +1475,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1493,8 +1493,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1507,8 +1507,8 @@ dependencies = [ "lpc55-pac", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1525,8 +1525,8 @@ dependencies = [ "rand_chacha", "rand_core", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1546,8 +1546,8 @@ dependencies = [ "lpc55-pac", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1565,8 +1565,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1600,8 +1600,8 @@ dependencies = [ "static_assertions", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1634,8 +1634,8 @@ dependencies = [ "serde", "static_assertions", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1650,8 +1650,8 @@ dependencies = [ "num-traits", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1663,8 +1663,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1683,8 +1683,8 @@ dependencies = [ "serde", "stage0-handoff", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1702,8 +1702,8 @@ dependencies = [ "nb 1.0.0", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1728,8 +1728,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1741,8 +1741,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1758,8 +1758,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1786,8 +1786,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1804,8 +1804,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1823,8 +1823,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1848,8 +1848,8 @@ dependencies = [ "serde", "serde_json", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1865,8 +1865,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1880,8 +1880,8 @@ dependencies = [ "num-traits", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1898,8 +1898,8 @@ dependencies = [ "userlib", "vsc7448", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1908,8 +1908,8 @@ version = "0.1.0" dependencies = [ "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1919,8 +1919,8 @@ dependencies = [ "drv-onewire", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1932,8 +1932,8 @@ dependencies = [ "idol", "ringbuf", "tlvc", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2002,8 +2002,8 @@ dependencies = [ "num-traits", "rand_core", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2022,8 +2022,8 @@ dependencies = [ "num-traits", "ringbuf", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2037,8 +2037,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2062,8 +2062,8 @@ dependencies = [ "userlib", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2086,8 +2086,8 @@ dependencies = [ "serde", "serde_json", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2114,8 +2114,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2143,8 +2143,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2157,8 +2157,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2179,8 +2179,8 @@ dependencies = [ "ringbuf", "task-config", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2192,8 +2192,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2222,8 +2222,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2254,8 +2254,8 @@ dependencies = [ "tlvc", "unwrap-lite", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2266,8 +2266,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2279,8 +2279,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2294,8 +2294,8 @@ dependencies = [ "num-traits", "stm32g0", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2342,8 +2342,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2361,8 +2361,8 @@ dependencies = [ "num-traits", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2373,8 +2373,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2389,9 +2389,10 @@ dependencies = [ "num-traits", "ringbuf", "stm32h7", + "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2402,8 +2403,8 @@ dependencies = [ "ringbuf", "stm32h7", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2418,8 +2419,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2449,7 +2450,7 @@ dependencies = [ "stm32h7", "syn 2.0.98", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2476,8 +2477,8 @@ dependencies = [ "ssmarshal", "static-cell", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2504,8 +2505,8 @@ dependencies = [ "serde", "stage0-handoff", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2526,8 +2527,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2545,8 +2546,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2590,8 +2591,8 @@ dependencies = [ "stm32h7", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2608,8 +2609,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2633,8 +2634,8 @@ dependencies = [ "task-sensor-api", "transceiver-messages", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2667,8 +2668,8 @@ dependencies = [ "task-thermal-api", "transceiver-messages", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2685,8 +2686,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2707,8 +2708,8 @@ dependencies = [ "stm32f4", "task-config", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2720,8 +2721,8 @@ dependencies = [ "idol", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2738,8 +2739,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2755,8 +2756,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2844,7 +2845,7 @@ name = "endoscope-abi" version = "0.1.0" dependencies = [ "sha3", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2986,6 +2987,14 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "gateway-ereport-messages" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/management-gateway-service#57ffd1c24f3ad1919fb4a605d5a501f2c5deb54c" +dependencies = [ + "zerocopy 0.8.26", +] + [[package]] name = "gateway-messages" version = "0.1.0" @@ -3001,7 +3010,7 @@ dependencies = [ "strum", "strum_macros", "uuid", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3237,8 +3246,8 @@ dependencies = [ "serde_repr", "static_assertions", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3307,8 +3316,8 @@ dependencies = [ "serde", "serde-big-array 0.5.1", "static_assertions", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3364,7 +3373,7 @@ source = "git+https://github.com/oxidecomputer/idolatry.git#b9fb439a8b6d77d10c92 dependencies = [ "counters", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3487,8 +3496,8 @@ dependencies = [ "ssmarshal", "syn 2.0.98", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3542,8 +3551,8 @@ dependencies = [ "static_assertions", "unwrap-lite", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", "zeroize", ] @@ -3607,8 +3616,8 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3664,8 +3673,8 @@ dependencies = [ "static_assertions", "toml", "unwrap-lite", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", "zeroize", ] @@ -3696,8 +3705,8 @@ dependencies = [ "task-jefe-api", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -3850,6 +3859,12 @@ dependencies = [ "minicbor-derive", ] +[[package]] +name = "minicbor" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb9d59e79ad66121ab441a0d1950890906a41e01ae14145ecf8401aa8894f50" + [[package]] name = "minicbor-derive" version = "0.15.3" @@ -3867,7 +3882,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "becf18ac384ecf6f53b2db3b1549eebff664c67ecf259ae99be5912193291686" dependencies = [ - "minicbor", + "minicbor 0.25.1", "serde", ] @@ -4093,7 +4108,7 @@ version = "0.1.0" dependencies = [ "hubpack", "serde", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -4926,6 +4941,7 @@ dependencies = [ name = "snitch-core" version = "0.1.0" dependencies = [ + "counters", "heapless", "minicbor-serde", "serde", @@ -5121,8 +5137,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5207,8 +5223,8 @@ dependencies = [ "static-cell", "unwrap-lite", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5274,8 +5290,8 @@ dependencies = [ "task-vpd-api", "update-buffer", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5292,8 +5308,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5315,8 +5331,8 @@ dependencies = [ "task-packrat-api", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5342,8 +5358,8 @@ dependencies = [ "task-jefe-api", "task-net-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5362,8 +5378,24 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + +[[package]] +name = "task-ereportulator" +version = "0.1.0" +dependencies = [ + "counters", + "idol", + "idol-runtime", + "minicbor 0.26.4", + "num-traits", + "ringbuf", + "task-packrat-api", + "userlib", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5446,8 +5478,8 @@ dependencies = [ "static-cell", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5494,8 +5526,8 @@ dependencies = [ "task-sensor-api", "tlvc", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5510,8 +5542,8 @@ dependencies = [ "num-traits", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5543,8 +5575,8 @@ dependencies = [ "ssmarshal", "task-jefe-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5562,8 +5594,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5593,8 +5625,8 @@ dependencies = [ "vsc7448", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5641,8 +5673,8 @@ dependencies = [ "userlib", "vsc7448-pac", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5664,8 +5696,8 @@ dependencies = [ "smoltcp", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5689,35 +5721,44 @@ dependencies = [ "anyhow", "build-util", "cfg-if", + "counters", "drv-cpu-seq-api", + "drv-rng-api", + "gateway-ereport-messages", + "hubris-task-names", "idol", "idol-runtime", + "minicbor 0.26.4", "mutable-statics", "num-traits", + "quote", "ringbuf", + "snitch-core", "spd", "static-cell", "static_assertions", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] name = "task-packrat-api" version = "0.1.0" dependencies = [ + "anyhow", "counters", "derive-idol-err", + "gateway-ereport-messages", "host-sp-messages", "idol", "idol-runtime", "num-traits", "oxide-barcode", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5769,8 +5810,8 @@ dependencies = [ "task-power-api", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5788,8 +5829,8 @@ dependencies = [ "static_assertions", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5812,8 +5853,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5833,8 +5874,8 @@ dependencies = [ "num-traits", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5851,8 +5892,25 @@ dependencies = [ "ringbuf", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", +] + +[[package]] +name = "task-snitch" +version = "0.1.0" +dependencies = [ + "anyhow", + "build-util", + "counters", + "gateway-ereport-messages", + "idol-runtime", + "static-cell", + "task-net-api", + "task-packrat-api", + "userlib", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5904,8 +5962,8 @@ dependencies = [ "task-sensor-api", "task-thermal-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5923,8 +5981,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5955,8 +6013,8 @@ dependencies = [ "task-net-api", "task-packrat-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5980,8 +6038,8 @@ dependencies = [ "idol", "task-net-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6003,8 +6061,8 @@ dependencies = [ "serde", "task-validate-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6022,8 +6080,8 @@ dependencies = [ "serde", "task-sensor-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6043,8 +6101,8 @@ dependencies = [ "ringbuf", "task-vpd-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6058,8 +6116,8 @@ dependencies = [ "idol-runtime", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6088,8 +6146,8 @@ dependencies = [ "build-util", "num-traits", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6102,8 +6160,8 @@ dependencies = [ "num-traits", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6118,8 +6176,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6134,8 +6192,8 @@ dependencies = [ "ssmarshal", "test-idol-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6151,8 +6209,8 @@ dependencies = [ "ringbuf", "test-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6172,8 +6230,8 @@ dependencies = [ "test-api", "test-idol-api", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6461,8 +6519,8 @@ dependencies = [ "ssmarshal", "unwrap-lite", "volatile-const", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6551,8 +6609,8 @@ dependencies = [ "userlib", "vsc-err", "vsc7448-pac", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6940,7 +6998,7 @@ dependencies = [ "toml-task", "toml_edit", "walkdir", - "zerocopy 0.8.25", + "zerocopy 0.8.26", "zip", ] @@ -6966,11 +7024,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "zerocopy-derive 0.8.25", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6997,9 +7055,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1717c7b81e..de44778dd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ leb128 = { version = "0.2.5", default-features = false } lpc55-pac = { version = "0.4", default-features = false } memchr = { version = "2.4", default-features = false } memoffset = { version = "0.6.5", default-features = false } +minicbor = { version = "0.26.4", default-features = false } multimap = { version = "0.8.3", default-features = false } nb = { version = "1", default-features = false } num = { version = "0.4", default-features = false } @@ -144,6 +145,7 @@ zip = { version = "0.6", default-features = false, features = ["bzip2", "deflate attest-data = { git = "https://github.com/oxidecomputer/dice-util", default-features = false, version = "0.4.0" } dice-mfg-msgs = { git = "https://github.com/oxidecomputer/dice-util", default-features = false, version = "0.2.1" } gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", default-features = false, features = ["smoltcp"] } +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", default-features = false } gimlet-inspector-protocol = { git = "https://github.com/oxidecomputer/gimlet-inspector-protocol", version = "0.1.0" } hif = { git = "https://github.com/oxidecomputer/hif", default-features = false } humpty = { git = "https://github.com/oxidecomputer/humpty", default-features = false, version = "0.1.3" } diff --git a/app/cosmo/base.toml b/app/cosmo/base.toml index ce6d2d93a7..69fe3f1bcb 100644 --- a/app/cosmo/base.toml +++ b/app/cosmo/base.toml @@ -122,10 +122,20 @@ notifications = ["i2c1-irq", "i2c2-irq", "i2c3-irq", "i2c4-irq"] [tasks.packrat] name = "task-packrat" priority = 1 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] -features = ["cosmo"] +features = ["cosmo", "ereport"] + +[tasks.rng_driver] +features = ["h753", "ereport"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] [tasks.thermal] name = "task-thermal" @@ -347,6 +357,17 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.spd] name = "task-cosmo-spd" priority = 7 @@ -1456,6 +1477,15 @@ port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [config.auxflash] memory-size = 33_554_432 # 256 Mib / 32 MiB slot-count = 16 # 2 MiB slots diff --git a/app/demo-stm32h7-nucleo/app-h753.toml b/app/demo-stm32h7-nucleo/app-h753.toml index dd3e0c1a4b..53336da6b7 100644 --- a/app/demo-stm32h7-nucleo/app-h753.toml +++ b/app/demo-stm32h7-nucleo/app-h753.toml @@ -71,7 +71,6 @@ task-slots = ["sys"] [tasks.packrat] name = "task-packrat" priority = 2 -max-sizes = {flash = 8192, ram = 2048} start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/gimlet/base.toml b/app/gimlet/base.toml index d9be84bc8c..f8fe261bb6 100644 --- a/app/gimlet/base.toml +++ b/app/gimlet/base.toml @@ -123,10 +123,11 @@ notifications = ["i2c1-irq", "jefe-state-change"] [tasks.packrat] name = "task-packrat" priority = 1 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] -features = ["gimlet"] +features = ["gimlet", "ereport"] [tasks.thermal] name = "task-thermal" @@ -194,6 +195,15 @@ interrupts = {"hash.irq" = "hash-irq"} task-slots = ["sys"] notifications = ["hash-irq"] +[tasks.rng_driver] +features = ["h753", "ereport"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] + [tasks.hf] name = "drv-gimlet-hf-server" features = ["h753"] @@ -336,6 +346,17 @@ extern-regions = ["sram1", "sram2", "sram3", "sram4"] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.sbrmi] name = "drv-sbrmi" priority = 4 @@ -1315,3 +1336,11 @@ port = 23547 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 512 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } diff --git a/app/gimletlet/app-ereportlet.toml b/app/gimletlet/app-ereportlet.toml new file mode 100644 index 0000000000..c303920857 --- /dev/null +++ b/app/gimletlet/app-ereportlet.toml @@ -0,0 +1,31 @@ +# Gimletlet Ereport test application +# +# This image includes the `ereportulator` task, which may be used to generate +# fake error reports to test the ereport aggregation and evacuation subsystem. +# +# Ereports may be generated using `humility hiffy` to call the +# `Ereportulator.fake_ereport` IPC operation. This takes one argument, `n`, +# which is an arbitrary `u32` value included in the ereport data payload. This +# is intended to be used to differentiate between multiple ereports during +# testing. +# +# For example: +# +# $ humility hiffy -t gimletlet hiffy -c Ereportulator.fake_ereport -a n=42 +# +name = "gimletlet-ereportlet" +inherit = "app.toml" + +[tasks.jefe.config.allowed-callers] +request_reset = ["hiffy"] + +[tasks.hiffy] +features = ["h753", "stm32h7", "i2c", "gpio", "spi"] +task-slots = ["sys", "i2c_driver", "user_leds", "ereportulator"] + +[tasks.ereportulator] +name = "task-ereportulator" +priority = 5 +start = true +task-slots = ["packrat"] +notifications = [] diff --git a/app/gimletlet/app.toml b/app/gimletlet/app.toml index 006e149568..fa5e535323 100644 --- a/app/gimletlet/app.toml +++ b/app/gimletlet/app.toml @@ -42,11 +42,12 @@ owner = {name = "sprot", notification = "rot_irq"} [tasks.packrat] name = "task-packrat" -priority = 3 -max-sizes = {flash = 8192, ram = 2048} +priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] +stacksize = 1040 +features = ["ereport"] [tasks.control_plane_agent] name = "task-control-plane-agent" @@ -185,6 +186,21 @@ task-slots = ["net", "packrat"] features = ["vlan"] notifications = ["socket"] +[tasks.snitch] +name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 4 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.rng_driver] +features = ["h753", "ereport"] +task-slots = ["sys", "user_leds", "packrat"] + # VLAN configuration [config.net.vlans.sidecar1] vid = 0x301 @@ -233,6 +249,15 @@ port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [tasks.sprot] name = "drv-stm32h7-sprot-server" priority = 5 diff --git a/app/grapefruit/app-dev.toml b/app/grapefruit/app-dev.toml index 9d1a066fa1..9109c815e9 100644 --- a/app/grapefruit/app-dev.toml +++ b/app/grapefruit/app-dev.toml @@ -6,3 +6,43 @@ inherit = "base.toml" features = ["uart8"] uses = ["uart8"] interrupts = {"uart8.irq" = "usart-irq"} + +# Ereport stuff +[tasks.packrat] +stacksize = 1040 +features = ["ereport"] + +[tasks.snitch] +name = "task-snitch" +priority = 4 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + +[tasks.rng_driver] +features = ["h753", "ereport"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] + +# Demo/test task for ereports +[tasks.ereportulator] +name = "task-ereportulator" +priority = 6 +start = true +task-slots = ["packrat"] +notifications = [] + +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } diff --git a/app/grapefruit/base.toml b/app/grapefruit/base.toml index cd29fec20f..3490f1f076 100644 --- a/app/grapefruit/base.toml +++ b/app/grapefruit/base.toml @@ -108,8 +108,7 @@ notifications = ["timer"] [tasks.packrat] name = "task-packrat" -priority = 3 -max-sizes = {flash = 8192, ram = 2048} +priority = 1 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] diff --git a/app/psc/base.toml b/app/psc/base.toml index 64270506fa..9c343bdf41 100644 --- a/app/psc/base.toml +++ b/app/psc/base.toml @@ -90,6 +90,14 @@ owner = {name = "sequencer", notification = "psu_pwr_ok_6"} +[tasks.rng_driver] +features = ["h753", "ereport"] +name = "drv-stm32h7-rng" +priority = 6 +uses = ["rng"] +start = true +stacksize = 512 +task-slots = ["sys", "packrat"] [tasks.i2c_driver] name = "drv-stm32xx-i2c-server" @@ -109,11 +117,12 @@ notifications = ["i2c2-irq", "i2c3-irq"] [tasks.packrat] name = "task-packrat" -priority = 3 -max-sizes = {flash = 8192, ram = 2048} +priority = 1 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] +features = ["ereport"] [tasks.sequencer] name = "drv-psc-seq-server" @@ -313,6 +322,17 @@ extern-regions = [ "sram1", "sram2", "sram3", "sram4" ] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 5 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.idle] name = "task-idle" priority = 7 @@ -535,3 +555,12 @@ owner = {name = "dump_agent", notification = "socket"} port = 11113 tx = { packets = 3, bytes = 1024 } rx = { packets = 3, bytes = 1024 } + +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } diff --git a/app/sidecar/base.toml b/app/sidecar/base.toml index c399811049..02561d8d88 100644 --- a/app/sidecar/base.toml +++ b/app/sidecar/base.toml @@ -256,11 +256,12 @@ notifications = ["socket", "timer"] [tasks.packrat] name = "task-packrat" -priority = 3 -max-sizes = {flash = 8192, ram = 2048} +priority = 1 +stacksize = 1040 start = true # task-slots is explicitly empty: packrat should not send IPCs! task-slots = [] +features = ["ereport"] [tasks.sequencer] name = "drv-sidecar-seq-server" @@ -333,6 +334,17 @@ extern-regions = [ "sram1", "sram2", "sram3", "sram4" ] notifications = ["socket"] features = ["net", "vlan"] +[tasks.snitch] +name = "task-snitch" +# The snitch should have a priority immediately below that of the net task, +# to minimize the number of components that can starve it from resources. +priority = 6 +stacksize = 1200 +start = true +task-slots = ["net", "packrat"] +features = ["vlan"] +notifications = ["socket"] + [tasks.idle] name = "task-idle" priority = 8 @@ -1163,6 +1175,15 @@ port = 11112 tx = { packets = 3, bytes = 2048 } rx = { packets = 3, bytes = 2048 } +[config.net.sockets.ereport] +kind = "udp" +owner = {name = "snitch", notification = "socket"} +port = 57005 +tx = { packets = 3, bytes = 1024 } +# v0 ereport requests are always 35B, so just make the buffer exactly +# that size... +rx = { packets = 3, bytes = 35 } + [config.auxflash] memory-size = 33_554_432 # 256 Mib / 32 MiB slot-count = 16 # 2 MiB slots diff --git a/drv/stm32h7-rng/Cargo.toml b/drv/stm32h7-rng/Cargo.toml index fa69fcebc4..1c02db57f0 100644 --- a/drv/stm32h7-rng/Cargo.toml +++ b/drv/stm32h7-rng/Cargo.toml @@ -12,6 +12,7 @@ zerocopy-derive = { workspace = true } drv-rng-api = { path = "../rng-api" } drv-stm32xx-sys-api = { path = "../stm32xx-sys-api" } +task-packrat-api = { path = "../../task/packrat-api", optional = true } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } ringbuf = { path = "../../lib/ringbuf" } counters = { version = "0.1.0", path = "../../lib/counters" } @@ -22,6 +23,7 @@ idol = { workspace = true } [features] h743 = ["stm32h7/stm32h743", "drv-stm32xx-sys-api/h743"] h753 = ["stm32h7/stm32h753", "drv-stm32xx-sys-api/h753"] +ereport = ["task-packrat-api"] no-ipc-counters = ["idol/no-counters"] # This section is here to discourage RLS/rust-analyzer from doing test builds, diff --git a/drv/stm32h7-rng/src/main.rs b/drv/stm32h7-rng/src/main.rs index 8981015a70..3ad29293d6 100644 --- a/drv/stm32h7-rng/src/main.rs +++ b/drv/stm32h7-rng/src/main.rs @@ -25,6 +25,9 @@ use userlib::*; task_slot!(SYS, sys); +#[cfg(feature = "ereport")] +task_slot!(PACKRAT, packrat); + counted_ringbuf!(Trace, 32, Trace::Blank); #[derive(Copy, Clone, Debug, Eq, PartialEq, counters::Count)] @@ -34,6 +37,8 @@ enum Trace { ClockError, SeedError, Recovered, + #[cfg(feature = "ereport")] + SetRestartId(Result<(), task_packrat_api::CacheSetError>), } struct Stm32h7Rng { @@ -228,11 +233,42 @@ impl NotificationHandler for Stm32h7RngServer { } } +#[cfg(feature = "ereport")] +fn generate_restart_id(rng: &mut Stm32h7Rng) { + use task_packrat_api::Packrat; + const BYTES: usize = 16; + + let mut bytes = [0u8; BYTES]; + for i in 0..(BYTES / 4) { + 'retry: loop { + match rng.read() { + Ok(word) => { + let word_bytes = &word.to_ne_bytes(); + bytes[i * 4..i * 4 + 4].copy_from_slice(word_bytes); + break 'retry; + } + Err(_) => { + // XXX(eliza): what do we do if this fails? + let _ = rng.attempt_recovery(); + } + } + } + } + + let id = u128::from_ne_bytes(bytes); + let packrat = Packrat::from(PACKRAT.get_task_id()); + let result = packrat.set_ereport_restart_id(id); + ringbuf_entry!(Trace::SetRestartId(result)); +} + #[export_name = "main"] fn main() -> ! { let mut rng = Stm32h7Rng::new(); rng.init(); + #[cfg(feature = "ereport")] + generate_restart_id(&mut rng); + let mut srv = Stm32h7RngServer::new(rng); let mut buffer = [0u8; idl::INCOMING_SIZE]; diff --git a/idl/ereportulator.idol b/idl/ereportulator.idol new file mode 100644 index 0000000000..30e71f9434 --- /dev/null +++ b/idl/ereportulator.idol @@ -0,0 +1,24 @@ +// Interface to the ereport demo task. +Interface( + name: "Ereportulator", + ops: { + "fake_ereport": ( + doc: "Make a fake error report.", + args: { + "n": "u32", + }, + reply: Simple("()"), + idempotent: true, + ), + "set_fake_vpd": ( + doc: "Send fake vital product data (VPD) to Packrat.", + args: { + }, + reply: Result( + ok: "()", + err: CLike("task_packrat_api::CacheSetError"), + ), + idempotent: true, + ), + } +) diff --git a/idl/packrat.idol b/idl/packrat.idol index 1c8386c9a6..a8568ac62d 100644 --- a/idl/packrat.idol +++ b/idl/packrat.idol @@ -94,6 +94,44 @@ Interface( reply: Simple("()"), idempotent: true, ), + "set_ereport_restart_id": ( + doc: "Set the restart ID for ereports", + args: { + "restart_id": "u128", + }, + reply: Result( + ok: "()", + err: CLike("CacheSetError"), + ), + idempotent: true, + ), + "deliver_ereport": ( + doc: "Hand an encoded ereport to packrat for buffering.", + args: { + }, + leases: { + "data": (type: "[u8]", read: true, max_len: Some(1024)), + }, + reply: Simple("()"), + idempotent: true, + ), + "read_ereports": ( + doc: "Read ereports starting with a watermark (ENA) value", + args: { + "request_id": "ereport_messages::RequestIdV0", + "restart_id": "ereport_messages::RestartId", + "start_ena": "ereport_messages::Ena", + "limit": "u8", + "committed_ena": "ereport_messages::Ena", + }, + leases: { + "data": (type: "[u8]", write: true), + }, + reply: Result( + ok: "usize", + err: CLike("EreportReadError"), + ), + idempotent: true, + ), }, ) - diff --git a/lib/snitch-core/Cargo.toml b/lib/snitch-core/Cargo.toml index 3b82c43bd2..ac352830cf 100644 --- a/lib/snitch-core/Cargo.toml +++ b/lib/snitch-core/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] heapless.workspace = true unwrap-lite = { version = "0.1.0", path = "../unwrap-lite" } +counters = { path = "../counters", optional = true} [lints] workspace = true diff --git a/lib/snitch-core/src/lib.rs b/lib/snitch-core/src/lib.rs index 9299a15683..5480a6011d 100644 --- a/lib/snitch-core/src/lib.rs +++ b/lib/snitch-core/src/lib.rs @@ -63,6 +63,13 @@ pub struct Store { stored_record_count: usize, } +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "counters", derive(counters::Count))] +pub enum InsertResult { + Inserted, + Lost, +} + impl Store { /// Empty store constant for use in static initializers. /// @@ -86,7 +93,8 @@ impl Store { // If the queue has never been touched, insert our "arbitrary data loss" // record into the stream to consume ENA 0. if !self.initialized() { - self.insert_impl( + // Should always succeed... + let _ = self.insert_impl( self.our_task_id, timestamp, // This is a canned CBOR message that decodes as... @@ -97,6 +105,8 @@ impl Store { // f6 # null / None &[0xA1, 0x64, 0x6C, 0x6F, 0x73, 0x74, 0xF6], ); + // ENAs start at 1. + self.earliest_ena = 1; } } @@ -108,16 +118,7 @@ impl Store { /// Returns the current free space in the queue, in bytes. This is raw; /// subtract 12 to get the largest single message that can be enqueued. pub fn free_space(&self) -> usize { - match self.insert_state { - InsertState::Collecting => N - self.storage.len(), - InsertState::Losing { .. } => { - // Indicate how much space we have after recovery succeeds, - // which may be zero if we can't recover yet. - (N - self.storage.len()) - .saturating_sub(OVERHEAD) - .saturating_sub(DATA_LOSS_LEN) - } - } + N - self.storage.len() } /// Inserts a record, or records it as lost. @@ -135,7 +136,18 @@ impl Store { /// implementation. This maximum size is larger than we expect our backing /// buffer to be. Any records larger than the max will be treated as not /// fitting. (Currently the max is 64 kiB.) - pub fn insert(&mut self, sender: u16, timestamp: u64, data: &[u8]) { + /// + /// # Returns + /// + /// - [`InsertResult::Inserted`] if the record was successfully inserted. + /// - [`InsertResult::Lost`] if the record was lost due to insufficient + /// space. + pub fn insert( + &mut self, + sender: u16, + timestamp: u64, + data: &[u8], + ) -> InsertResult { debug_assert!(self.initialized()); self.insert_impl(sender, timestamp, data) } @@ -189,11 +201,15 @@ impl Store { /// /// If the ENA is either lower than any of our stored records, or higher /// than what we've vended out, this is a no-op. - pub fn flush_thru(&mut self, last_written_ena: u64) { + /// + /// # Returns + /// + /// This method returns the number of records that were discarded. + pub fn flush_thru(&mut self, last_written_ena: u64) -> usize { let Some(index) = last_written_ena.checked_sub(self.earliest_ena) else { // Cool, we're already aware that record has been written. - return; + return 0; }; if index >= self.stored_record_count as u64 { // Uhhhh. We have not issued this ENA. It could not possibly have @@ -201,9 +217,10 @@ impl Store { // // TODO: is this an opportunity for the queue _itself_ to generate // a defect report? For now, we'll just ignore it. - return; + return 0; } + let mut discarded = 0; for _ in 0..=index { // Discard the lead record in the queue. let mut slices = self.storage.as_slices(); @@ -219,6 +236,7 @@ impl Store { self.stored_record_count -= 1; self.earliest_ena += 1; + discarded += 1; } // You might be curious why we don't do our loss recovery process here, @@ -227,6 +245,7 @@ impl Store { // full. If we are able to recover here, but do _not_ have enough bytes // free for the next incoming record, then we'd just kick back into loss // state.... necessitating another loss record. And so forth. + discarded } /// Makes a best effort at inserting a record. @@ -242,7 +261,18 @@ impl Store { /// /// If we are `Collecting` but `data` plus a header won't fit, we enter the /// `Losing` state with a count of 1. - fn insert_impl(&mut self, sender: u16, timestamp: u64, data: &[u8]) { + /// + /// # Returns + /// + /// - [`InsertResult::Inserted`] if the record was successfully inserted. + /// - [`InsertResult::Lost`] if the record was lost due to insufficient + /// space. + fn insert_impl( + &mut self, + sender: u16, + timestamp: u64, + data: &[u8], + ) -> InsertResult { // We attempt recovery here so that we can _avoid_ generating a loss // record if this next record (`data`) is just going to kick us back // into loss state. @@ -252,7 +282,7 @@ impl Store { match &mut self.insert_state { InsertState::Collecting => { - let room = self.storage.capacity() - self.storage.len(); + let room = self.free_space(); if data_len.is_some_and(|n| room >= OVERHEAD + n as usize) { self.write_header( data_len.unwrap_lite(), @@ -262,15 +292,18 @@ impl Store { for &byte in data { self.storage.push_back(byte).unwrap_lite(); } + InsertResult::Inserted } else { self.insert_state = InsertState::Losing { count: NonZeroU32::new(1).unwrap_lite(), timestamp, }; + InsertResult::Lost } } InsertState::Losing { count, .. } => { *count = count.saturating_add(1); + InsertResult::Lost } } } @@ -294,9 +327,17 @@ impl Store { fn recover_if_required(&mut self, space_required: Option) { // We only need to take action if we're in Losing state. if let InsertState::Losing { count, timestamp } = self.insert_state { - // Note: already includes OVERHEAD/DATA_LOSS_LEN let room = self.free_space(); - let required = space_required.unwrap_or(0); + + // We can recover only if there is room for both the required + // amount of space *and* the additional loss record we must + // insert in order to recover. + let required = + // The space we hope to be able to use after recovery + space_required.unwrap_or(0) + // The length of the loss record itself + + DATA_LOSS_LEN + OVERHEAD + ; if room >= required { // We can recover! self.write_header( @@ -433,7 +474,7 @@ mod tests { let &[r] = initial_contents.as_slice() else { panic!("missing initial loss record"); }; - assert_eq!(r.ena, 0); + assert_eq!(r.ena, 1); assert_eq!(r.tid, OUR_FAKE_TID); assert_eq!(r.timestamp, 1); @@ -456,7 +497,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); - assert_eq!(snapshot[0].ena, 1); + assert_eq!(snapshot[0].ena, 2); assert_eq!(snapshot[0].tid, ANOTHER_FAKE_TID); assert_eq!(snapshot[0].timestamp, 5); assert_eq!(snapshot[0].contents, b"hello, world!"); @@ -484,7 +525,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 64 - OVERHEAD] @@ -515,7 +556,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: OUR_FAKE_TID, timestamp: 5, contents: LossRecord { lost: Some(1) }, @@ -546,7 +587,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 28], @@ -556,7 +597,7 @@ mod tests { assert_eq!( snapshot[1].decode_as::(), Item { - ena: 2, + ena: 3, tid: OUR_FAKE_TID, timestamp: 10, // time when loss began contents: LossRecord { lost: Some(1) }, @@ -589,7 +630,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: OUR_FAKE_TID, timestamp: 5, // time of _first_ loss contents: LossRecord { lost: Some(11) }, @@ -597,6 +638,71 @@ mod tests { ); } + /// Tests that the buffer is never allowed to fill so much that it cannot + /// fit a loss record. This reproduces a panic where there was insufficient + /// space to record a loss record. + #[test] + fn data_loss_on_full_queue() { + let mut s = Store::<64>::DEFAULT; + s.initialize(OUR_FAKE_TID, 1); + consume_initial_loss(&mut s); + + // Fill half the buffer. + s.insert(ANOTHER_FAKE_TID, 5, &[0; 32 - OVERHEAD]); + // Try to fill the other half of the buffer, *to the brim*. After this + // record is accepted, we start losing data, but we cannot yet create a + // loss record until something is removed from the buffer. + s.insert(ANOTHER_FAKE_TID, 6, &[0; 32 - OVERHEAD]); + // This one definitely gets lost. + s.insert(ANOTHER_FAKE_TID, 7, &[0; 32 - OVERHEAD]); + + let snapshot: Vec>> = copy_contents_raw(&mut s); + assert_eq!(snapshot.len(), 2, "{snapshot:?}"); + assert_eq!( + snapshot[0], + Item { + ena: 2, + tid: ANOTHER_FAKE_TID, + timestamp: 5, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + assert_eq!( + snapshot[1], + Item { + ena: 3, + tid: ANOTHER_FAKE_TID, + timestamp: 6, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + + // Flush a record. We will now record the lost data, as there's now + // room for the loss record. + s.flush_thru(2); + + let snapshot: Vec>> = copy_contents_raw(&mut s); + assert_eq!(snapshot.len(), 2, "{snapshot:?}"); + assert_eq!( + snapshot[0], + Item { + ena: 3, + tid: ANOTHER_FAKE_TID, + timestamp: 6, + contents: Vec::from([0; 32 - OVERHEAD]) + } + ); + assert_eq!( + snapshot[1].decode_as::(), + Item { + ena: 4, + tid: OUR_FAKE_TID, + timestamp: 7, + contents: LossRecord { lost: Some(1) }, + } + ); + } + /// Arranges for the queue to contain: valid data; a loss record; more valid /// data. This helps exercise recovery behavior. #[test] @@ -619,7 +725,7 @@ mod tests { assert_eq!( snapshot[0], Item { - ena: 1, + ena: 2, tid: ANOTHER_FAKE_TID, timestamp: 5, contents: vec![0; 16], @@ -629,7 +735,7 @@ mod tests { assert_eq!( snapshot[1].decode_as::(), Item { - ena: 2, + ena: 3, tid: OUR_FAKE_TID, timestamp: 10, contents: LossRecord { lost: Some(1) }, @@ -639,7 +745,7 @@ mod tests { assert_eq!( snapshot[2], Item { - ena: 3, + ena: 4, tid: ANOTHER_FAKE_TID, timestamp: 15, contents: vec![0; 16], @@ -663,7 +769,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 5); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 1 + i as u64); + assert_eq!(rec.ena, 2 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 5 + i as u64); assert_eq!(rec.contents, &[i as u8]); @@ -678,7 +784,7 @@ mod tests { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 4); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 2 + i as u64); + assert_eq!(rec.ena, 3 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 6 + i as u64); assert_eq!(rec.contents, &[i as u8 + 1]); @@ -686,19 +792,19 @@ mod tests { } // Flush all but the last. - s.flush_thru(4); + s.flush_thru(5); { let snapshot = copy_contents_raw(&mut s); assert_eq!(snapshot.len(), 1); for (i, rec) in snapshot.iter().enumerate() { - assert_eq!(rec.ena, 5 + i as u64); + assert_eq!(rec.ena, 6 + i as u64); assert_eq!(rec.tid, ANOTHER_FAKE_TID); assert_eq!(rec.timestamp, 9 + i as u64); assert_eq!(rec.contents, &[i as u8 + 4]); } } // Finally... - s.flush_thru(5); + s.flush_thru(6); assert_eq!(copy_contents_raw(&mut s), []); } @@ -709,23 +815,23 @@ mod tests { s.initialize(OUR_FAKE_TID, 1); consume_initial_loss(&mut s); - // This record occupies ENA 1 + // This record occupies ENA 2 s.insert(ANOTHER_FAKE_TID, 5, &[1]); - // ENA 2 + // ENA 3 s.insert(ANOTHER_FAKE_TID, 6, &[2]); assert_eq!(s.stored_record_count, 2); - // Flushing ENA 0 should have no effect (we already got rid of it) - s.flush_thru(0); + // Flushing ENA 1 should have no effect (we already got rid of it) + s.flush_thru(1); assert_eq!(s.stored_record_count, 2); - // Flushing ENA 1 should drop one record. - s.flush_thru(1); + // Flushing ENA 2 should drop one record. + s.flush_thru(2); assert_eq!(s.stored_record_count, 1); // 0 and 1 are both no-ops now. - for ena in [0, 1] { + for ena in [0, 1, 2] { s.flush_thru(ena); assert_eq!(s.stored_record_count, 1); } @@ -739,15 +845,15 @@ mod tests { s.initialize(OUR_FAKE_TID, 1); consume_initial_loss(&mut s); - // This record occupies ENA 1 + // This record occupies ENA 2 s.insert(ANOTHER_FAKE_TID, 5, &[1]); - // ENA 2 + // ENA 3 s.insert(ANOTHER_FAKE_TID, 6, &[2]); assert_eq!(s.stored_record_count, 2); - // ENA 3 has not yet been issued, and should not cause any change: - s.flush_thru(3); + // ENA 4 has not yet been issued, and should not cause any change: + s.flush_thru(4); assert_eq!(s.stored_record_count, 2); } @@ -756,7 +862,7 @@ mod tests { let &[r] = initial_contents.as_slice() else { panic!("missing initial loss record"); }; - assert_eq!(r.ena, 0); + assert_eq!(r.ena, 1); assert_eq!(r.tid, OUR_FAKE_TID); assert_eq!(r.timestamp, 1); diff --git a/sys/abi/src/lib.rs b/sys/abi/src/lib.rs index 7d7a28c5cf..3cf49d8909 100644 --- a/sys/abi/src/lib.rs +++ b/sys/abi/src/lib.rs @@ -87,6 +87,12 @@ impl From for Generation { } } +impl From for u8 { + fn from(x: Generation) -> Self { + x.0 + } +} + /// Newtype wrapper for an interrupt index #[derive( Copy, diff --git a/task/ereportulator/Cargo.toml b/task/ereportulator/Cargo.toml new file mode 100644 index 0000000000..1f0371b463 --- /dev/null +++ b/task/ereportulator/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "task-ereportulator" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = { path = "../../sys/userlib" } +idol-runtime.workspace = true +zerocopy.workspace = true +zerocopy-derive.workspace = true +task-packrat-api = { path = "../packrat-api" } +minicbor.workspace = true +num-traits.workspace = true +ringbuf = { path = "../../lib/ringbuf", features = ["counters"] } +counters = { path = "../../lib/counters" } + +[build-dependencies] +idol.workspace = true + +[[bin]] +name = "task-ereportulator" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/task/ereportulator/build.rs b/task/ereportulator/build.rs new file mode 100644 index 0000000000..a9c967c508 --- /dev/null +++ b/task/ereportulator/build.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> Result<(), Box> { + idol::Generator::new() + .with_counters( + idol::CounterSettings::default().with_server_counters(false), + ) + .build_server_support( + "../../idl/ereportulator.idol", + "server_stub.rs", + idol::server::ServerStyle::InOrder, + )?; + + Ok(()) +} diff --git a/task/ereportulator/src/main.rs b/task/ereportulator/src/main.rs new file mode 100644 index 0000000000..42483faa02 --- /dev/null +++ b/task/ereportulator/src/main.rs @@ -0,0 +1,161 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! +//! # BEHOLD THE EREPORTULATOR! +//! +//! This is a demo/testing task for the ereport subsystem; it is not intended +//! to be included in production images. The ereportulator is a simple task for +//! generating fake ereports when requested via Hiffy, for testing purposes. +//! +//! Ereports are requested using the `Ereportulator.fake_ereport` IPC operation. +//! This takes one argument, `n`, which is an arbitrary `u32` value to include +//! in the ereport --- intended for differentiating between ereports generated +//! during a test. +//! +//! For example: +//! +//! ```console +//! $ humility -t gimletlet hiffy -c Ereportulator.fake_ereport -a n=420 +//! humility: WARNING: archive on command-line overriding archive in environment file +//! humility: attached to 0483:3754:000B00154D46501520383832 via ST-Link V3 +//! Ereportulator.fake_ereport() => () +//! +//! ``` +//! +//! In addition, when testing on systems which lack real vital product data +//! (VPD) EEPROMs, such as on Gimletlet, this task can be asked to send a +//! made-up VPD identity to packrat. This way, ereports generated in testing +//! can have realistic-looking VPD metadata. Fake VPD is requested using the +//! `Ereportulator.set_fake_vpd` IPC operation: +//! +//! ```console +//! $ humility -t gimletlet hiffy -c Ereportulator.set_fake_vpd +//! humility: WARNING: archive on command-line overriding archive in environment file +//! humility: attached to 0483:3754:000B00154D46501520383832 via ST-Link V3 +//! Ereportulator.set_fake_vpd() => () +//! +//! ``` +//! +//! +#![no_std] +#![no_main] + +use core::convert::Infallible; + +use idol_runtime::RequestError; +use minicbor::Encoder; +use ringbuf::{counted_ringbuf, ringbuf_entry}; +use task_packrat_api::Packrat; +use userlib::{task_slot, RecvMessage, UnwrapLite}; + +task_slot!(PACKRAT, packrat); + +#[derive(Copy, Clone, Eq, PartialEq, counters::Count)] +enum Trace { + #[count(skip)] + None, + + SetFakeVpd(#[count(children)] Result<(), task_packrat_api::CacheSetError>), + EreportRequested(u32), + EreportDelivered { + encoded_len: usize, + }, +} + +counted_ringbuf!(Trace, 16, Trace::None); + +#[export_name = "main"] +fn main() -> ! { + let packrat = Packrat::from(PACKRAT.get_task_id()); + + let mut server = ServerImpl { + buf: [0; 256], + packrat, + }; + + let mut buffer = [0; idl::INCOMING_SIZE]; + + loop { + idol_runtime::dispatch(&mut buffer, &mut server); + } +} + +struct ServerImpl { + buf: [u8; 256], + packrat: Packrat, +} + +impl idl::InOrderEreportulatorImpl for ServerImpl { + fn fake_ereport( + &mut self, + _msg: &RecvMessage, + n: u32, + ) -> Result<(), RequestError> { + ringbuf_entry!(Trace::EreportRequested(n)); + + let encoded_len = { + let c = minicbor::encode::write::Cursor::new(&mut self.buf[..]); + let mut encoder = Encoder::new(c); + + // It's bad on purpose to make you click, Cliff! + encoder + .begin_map() + .unwrap_lite() + .str("k") + .unwrap_lite() + .str("test.ereport.please.ignore") + .unwrap_lite() + .str("badness") + .unwrap_lite() + .u32(n) + .unwrap_lite() + .str("msg") + .unwrap_lite() + .str("im dead") + .unwrap_lite() + .end() + .unwrap_lite(); + + encoder.into_writer().position() + }; + + self.packrat.deliver_ereport(&self.buf[..encoded_len]); + ringbuf_entry!(Trace::EreportDelivered { encoded_len }); + + Ok(()) + } + + fn set_fake_vpd( + &mut self, + _msg: &RecvMessage, + ) -> Result<(), RequestError> { + let result = self.packrat.set_identity(task_packrat_api::VpdIdentity { + part_number: *b"LOLNO000000", + serial: *b"69426661337", + revision: 42, + }); + + ringbuf_entry!(Trace::SetFakeVpd(result)); + + result?; + + Ok(()) + } +} + +impl idol_runtime::NotificationHandler for ServerImpl { + fn current_notification_mask(&self) -> u32 { + // We don't use notifications, don't listen for any. + 0 + } + + fn handle_notification(&mut self, _bits: u32) { + unreachable!() + } +} + +mod idl { + include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); +} diff --git a/task/packrat-api/Cargo.toml b/task/packrat-api/Cargo.toml index 91e00b0ef1..21b6794324 100644 --- a/task/packrat-api/Cargo.toml +++ b/task/packrat-api/Cargo.toml @@ -10,6 +10,7 @@ host-sp-messages.path = "../../lib/host-sp-messages" oxide-barcode.path = "../../lib/oxide-barcode" userlib.path = "../../sys/userlib" +gateway-ereport-messages.workspace = true idol-runtime.workspace = true num-traits.workspace = true zerocopy.workspace = true @@ -24,6 +25,7 @@ bench = false [build-dependencies] idol.workspace = true +anyhow.workspace = true [lints] workspace = true diff --git a/task/packrat-api/build.rs b/task/packrat-api/build.rs index 8249074539..8ef885bb70 100644 --- a/task/packrat-api/build.rs +++ b/task/packrat-api/build.rs @@ -2,10 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -fn main() -> Result<(), Box> { - idol::client::build_client_stub( - "../../idl/packrat.idol", - "client_stub.rs", - )?; +fn main() -> anyhow::Result<()> { + idol::client::build_client_stub("../../idl/packrat.idol", "client_stub.rs") + .map_err(|e| anyhow::anyhow!("{e}"))?; Ok(()) } diff --git a/task/packrat-api/src/lib.rs b/task/packrat-api/src/lib.rs index ea6a878335..e109705369 100644 --- a/task/packrat-api/src/lib.rs +++ b/task/packrat-api/src/lib.rs @@ -12,6 +12,7 @@ use zerocopy::{ FromBytes, Immutable, IntoBytes, KnownLayout, LittleEndian, U16, }; +pub use gateway_ereport_messages as ereport_messages; pub use host_sp_messages::HostStartupOptions; pub use oxide_barcode::VpdIdentity; @@ -52,4 +53,11 @@ pub enum CacheSetError { ValueAlreadySet = 1, } +#[derive( + Copy, Clone, Debug, FromPrimitive, Eq, PartialEq, IdolError, counters::Count, +)] +pub enum EreportReadError { + RestartIdNotSet = 1, +} + include!(concat!(env!("OUT_DIR"), "/client_stub.rs")); diff --git a/task/packrat/Cargo.toml b/task/packrat/Cargo.toml index 494be1a378..41556efce9 100644 --- a/task/packrat/Cargo.toml +++ b/task/packrat/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +gateway-ereport-messages.workspace = true idol-runtime.workspace = true num-traits.workspace = true spd.workspace = true @@ -12,17 +13,22 @@ zerocopy.workspace = true zerocopy-derive.workspace = true drv-cpu-seq-api = { path = "../../drv/cpu-seq-api", optional = true } +drv-rng-api = { path = "../../drv/rng-api", optional = true } +hubris-task-names = { path = "../../sys/task-names" } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } +counters = { path = "../../lib/counters" } static-cell = { path = "../../lib/static-cell" } task-packrat-api = { path = "../packrat-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } +snitch-core = { version = "0.1.0", path = "../../lib/snitch-core", optional = true, features = ["counters"] } +minicbor = { workspace = true, optional = true } [build-dependencies] anyhow.workspace = true cfg-if.workspace = true idol.workspace = true - +quote = { workspace = true, optional = true } build-util = { path = "../../build/util" } [features] @@ -31,6 +37,7 @@ gimlet = ["drv-cpu-seq-api"] grapefruit = [] boot-kmdb = [] no-ipc-counters = ["idol/no-counters"] +ereport = ["dep:drv-rng-api", "dep:snitch-core", "dep:minicbor", "dep:quote"] # This section is here to discourage RLS/rust-analyzer from doing test builds, # since test builds don't work for cross compilation. diff --git a/task/packrat/build.rs b/task/packrat/build.rs index 3a13c73984..681f142fd0 100644 --- a/task/packrat/build.rs +++ b/task/packrat/build.rs @@ -1,8 +1,12 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use anyhow::{anyhow, Result}; -fn main() -> Result<(), Box> { +#[cfg(feature = "ereport")] +use anyhow::Context; + +fn main() -> Result<()> { idol::Generator::new() .with_counters( idol::CounterSettings::default().with_server_counters(false), @@ -11,7 +15,8 @@ fn main() -> Result<(), Box> { "../../idl/packrat.idol", "server_stub.rs", idol::server::ServerStyle::InOrder, - )?; + ) + .map_err(|e| anyhow!("{e}"))?; // Ensure the "gimlet" feature is enabled on gimlet boards. #[cfg(not(feature = "gimlet"))] @@ -39,5 +44,45 @@ fn main() -> Result<(), Box> { _ => (), } + #[cfg(feature = "ereport")] + gen_ereport_config().context("failed to generate ereport config")?; + + Ok(()) +} + +#[cfg(feature = "ereport")] +fn gen_ereport_config() -> Result<()> { + use std::io::Write; + + let our_name = build_util::task_name(); + let tasks = build_util::task_ids(); + let id = tasks.get(&our_name).ok_or_else(|| { + anyhow!( + "task ID for {our_name:?} not found in task IDs map; this is \ + probably a bug in the build system", + ) + })?; + let id = u16::try_from(id).with_context(|| { + format!( + "packrat's task ID ({id}) exceeds u16::MAX, this is definitely \ + a bug" + ) + })?; + + let out_dir = build_util::out_dir(); + let dest_path = out_dir.join("ereport_config.rs"); + + let mut out = std::fs::File::create(&dest_path).with_context(|| { + format!("failed to create file {}", dest_path.display()) + })?; + writeln!( + out, + "{}", + quote::quote! { + pub(crate) const TASK_ID: u16 = #id; + } + ) + .with_context(|| format!("failed to write to {}", dest_path.display()))?; + Ok(()) } diff --git a/task/packrat/src/ereport.rs b/task/packrat/src/ereport.rs new file mode 100644 index 0000000000..48b7431acb --- /dev/null +++ b/task/packrat/src/ereport.rs @@ -0,0 +1,412 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Packrat ereport aggregation. +//! +//! As described in [RFD 545 § 4.3], `packrat`'s role in the ereport subsystem +//! is to aggregate ereports from other tasks in a circular buffer. Ereports are +//! submitted to `packrat` via the `deliver_ereport` IPC call. The `snitch` task +//! requests ereports from `packrat` using the `read_ereports` IPC call, which +//! also flushes committed ereports from the buffer. +//! +//! [RFD 545 § 4.3]: https://rfd.shared.oxide.computer/rfd/0545#_aggregation + +use super::ereport_messages; + +use core::convert::Infallible; +use idol_runtime::{ClientError, Leased, LenLimit, RequestError}; +use minicbor::CborLen; +use ringbuf::{counted_ringbuf, ringbuf_entry}; +use task_packrat_api::{EreportReadError, VpdIdentity}; +use userlib::{kipc, sys_get_timer, RecvMessage, TaskId}; +use zerocopy::IntoBytes; + +pub(crate) struct EreportStore { + storage: &'static mut snitch_core::Store, + recv: &'static mut [u8; RECV_BUF_SIZE], + image_id: [u8; 8], + pub(super) restart_id: Option, +} + +pub(crate) struct EreportBufs { + storage: snitch_core::Store, + recv: [u8; RECV_BUF_SIZE], +} + +/// Number of bytes of RAM dedicated to ereport storage. Each individual +/// report consumes a small amount of this (currently 12 bytes). +const STORE_SIZE: usize = 4096; + +/// Number of bytes for the receive buffer. This only needs to fit a single +/// ereport at a time (and implicitly, limits the maximum size of an ereport). +pub(crate) const RECV_BUF_SIZE: usize = 1024; + +/// Separate ring buffer for ereport events, as we probably don't care that much +/// about the sequence of ereport events relative to other packrat API events. +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum Trace { + #[count(skip)] + None, + EreportReceived { + src: TaskId, + len: u32, + #[count(children)] + result: snitch_core::InsertResult, + }, + ReadRequest { + restart_id: u128, + }, + Flushed { + committed_ena: u64, + flushed: usize, + }, + RestartIdMismatch { + current_restart_id: u128, + }, + MetadataError(#[count(children)] MetadataError), + MetadataEncoded { + len: u32, + }, + EreportError(#[count(children)] EreportError), + Reported { + start_ena: u64, + reports: u8, + limit: u8, + }, +} + +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum MetadataError { + TooLong, + PartNumberNotUtf8, + SerialNumberNotUtf8, +} + +#[derive(Copy, Clone, PartialEq, Eq, counters::Count)] +enum EreportError { + TaskIdOutOfRange, + TooLong, +} + +counted_ringbuf!(Trace, 16, Trace::None); + +impl EreportStore { + pub(crate) fn new( + EreportBufs { + ref mut storage, + ref mut recv, + }: &'static mut EreportBufs, + ) -> Self { + let now = sys_get_timer().now; + storage.initialize(config::TASK_ID, now); + let image_id = { + let id = kipc::read_image_id(); + u64::to_le_bytes(id) + }; + + Self { + storage, + recv, + image_id, + restart_id: None, + } + } +} + +impl EreportStore { + pub(crate) fn deliver_ereport( + &mut self, + msg: &RecvMessage, + data: LenLimit, RECV_BUF_SIZE>, + ) -> Result<(), RequestError> { + data.read_range(0..data.len(), self.recv) + .map_err(|_| ClientError::WentAway.fail())?; + let timestamp = sys_get_timer().now; + let result = self.storage.insert( + msg.sender.0, + timestamp, + &self.recv[..data.len()], + ); + ringbuf_entry!(Trace::EreportReceived { + src: msg.sender, + len: data.len() as u32, + result, + }); + Ok(()) + } + + pub(crate) fn read_ereports( + &mut self, + request_id: ereport_messages::RequestIdV0, + restart_id: ereport_messages::RestartId, + begin_ena: ereport_messages::Ena, + limit: u8, + committed_ena: ereport_messages::Ena, + data: Leased, + vpd: Option<&VpdIdentity>, + ) -> Result> { + ringbuf_entry!(Trace::ReadRequest { + restart_id: restart_id.into() + }); + + // XXX(eliza): in theory it might be nicer to use + // `minicbor::data::Token::{BeginArray, BeginMap, Break}` for these, but + // it's way more annoying in practice, because you need to have an + // `Encoder` and can't just put it in the buffer. + /// Byte indicating the beginning of an indeterminate-length CBOR + /// array. + const CBOR_BEGIN_ARRAY: u8 = 0x9f; + /// Byte indicating the beginning of an indeterminate-length CBOR map. + const CBOR_BEGIN_MAP: u8 = 0xbf; + /// Byte indicating the end of an indeterminate-length CBOR array or + /// map. + const CBOR_BREAK: u8 = 0xff; + + let current_restart_id = + self.restart_id.ok_or(EreportReadError::RestartIdNotSet)?; + // Skip over a header-sized initial chunk. + let first_data_byte = size_of::(); + + let mut position = first_data_byte; + let mut first_written_ena = None; + + // Start the metadata map. + // + // MGS expects us to always include this, and to just have it be + // empty if we didn't send any metadata. + data.write_at(position, CBOR_BEGIN_MAP) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + + // If the requested restart ID matches the current restart ID, then read + // from the requested ENA. If not, start at ENA 0. + let begin_ena = if restart_id == current_restart_id { + // If the restart ID matches, flush previous ereports up to + // `committed_ena`, if there is one. + if committed_ena != ereport_messages::Ena::NONE { + let flushed = self.storage.flush_thru(committed_ena.into()); + ringbuf_entry!(Trace::Flushed { + committed_ena: committed_ena.into(), + flushed, + }); + } + begin_ena.into() + } else { + ringbuf_entry!(Trace::RestartIdMismatch { + current_restart_id: current_restart_id.into() + }); + + // If we don't have our VPD identity yet, don't send any metadata. + // + // We *could* include the Hubris image ID here even if our VPD + // identity hasn't been set, but sending an empty metadata map + // ensures that MGS will continue asking for metadata on subsequent + // requests. + if let Some(vpd) = vpd { + // Encode the metadata map into our buffer. + match self.encode_metadata(vpd) { + Ok(encoded) => { + data.write_range( + position..position + encoded.len(), + encoded, + ) + .map_err(|_| ClientError::WentAway.fail())?; + + position += encoded.len(); + + ringbuf_entry!(Trace::MetadataEncoded { + len: encoded.len() as u32 + }); + } + Err(err) => { + // Encoded VPD metadata was too long, or couldn't be + // represented as a CBOR string. + ringbuf_entry!(Trace::MetadataError(err)); + } + } + } + // Begin at ENA 0 + 0 + }; + + // End metadata map. + data.write_at(position, CBOR_BREAK) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + + let mut reports = 0; + // Beginning with the first + for r in self.storage.read_from(begin_ena) { + if reports >= limit { + break; + } + + if first_written_ena.is_none() { + first_written_ena = Some(r.ena); + // Start the ereport array + data.write_at(position, CBOR_BEGIN_ARRAY) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + } + + let tid = TaskId(r.tid); + let task_name = hubris_task_names::TASK_NAMES + .get(tid.index()) + .copied() + .unwrap_or_else(|| { + // This represents an internal error, where we've recorded + // an out-of-range task ID somehow. We still want to get the + // ereport out, so we'll use a recognizable but illegal task + // name to indicate that it's missing. + ringbuf_entry!(Trace::EreportError( + EreportError::TaskIdOutOfRange + )); + "-" // TODO + }); + let generation = tid.generation(); + + let entry = ( + task_name, + u8::from(generation), + r.timestamp, + ByteGather(r.slices.0, r.slices.1), + ); + let mut c = + minicbor::encode::write::Cursor::new(&mut self.recv[..]); + match minicbor::encode(&entry, &mut c) { + Ok(()) => { + let size = c.position(); + // If there's no room left for this one in the lease, we're + // done here. Note that the use of `>=` rather than `>` is + // intentional, as we want to ensure that there's room for + // the final `CBOR_BREAK` byte that ends the CBOR array. + if position + size >= data.len() { + break; + } + data.write_range( + position..position + size, + &self.recv[..size], + ) + .map_err(|_| ClientError::WentAway.fail())?; + position += size; + reports += 1; + } + Err(_end) => { + // This is an odd one; we've admitted a record into our + // queue that won't fit in our buffer. This can happen + // because of the encoding overhead, in theory, but should + // be prevented. + ringbuf_entry!(Trace::EreportError(EreportError::TooLong)); + } + } + } + + if let Some(start_ena) = first_written_ena { + // End CBOR array, if we wrote anything. + data.write_at(position, CBOR_BREAK) + .map_err(|_| ClientError::WentAway.fail())?; + position += 1; + + ringbuf_entry!(Trace::Reported { + start_ena, + reports, + limit + }); + } + + let first_ena = first_written_ena.unwrap_or(0); + let header = ereport_messages::ResponseHeader::V0( + ereport_messages::ResponseHeaderV0 { + request_id, + restart_id: current_restart_id, + start_ena: first_ena.into(), + }, + ); + data.write_range(0..size_of_val(&header), header.as_bytes()) + .map_err(|_| ClientError::WentAway.fail())?; + Ok(position) + } + + fn encode_metadata( + &mut self, + vpd: &VpdIdentity, + ) -> Result<&[u8], MetadataError> { + let c = minicbor::encode::write::Cursor::new(&mut self.recv[..]); + let mut encoder = minicbor::Encoder::new(c); + // TODO(eliza): presently, this code bails out if the metadata map gets + // longer than our buffer. It would be nice to have a way to keep the + // encoded metadata up to the last complete key-value pair... + encoder + .str("hubris_archive_id")? + .bytes(&self.image_id[..])?; + match core::str::from_utf8(&vpd.part_number[..]) { + Ok(part_number) => { + encoder.str("baseboard_part_number")?.str(part_number)?; + } + Err(_) => ringbuf_entry!(Trace::MetadataError( + MetadataError::PartNumberNotUtf8 + )), + } + match core::str::from_utf8(&vpd.serial[..]) { + Ok(serial_number) => { + encoder.str("baseboard_serial_number")?.str(serial_number)?; + } + Err(_) => ringbuf_entry!(Trace::MetadataError( + MetadataError::SerialNumberNotUtf8 + )), + } + encoder.str("baseboard_rev")?.u32(vpd.revision)?; + let size = encoder.into_writer().position(); + Ok(&self.recv[..size]) + } +} + +impl EreportBufs { + pub(crate) const fn new() -> Self { + Self { + storage: snitch_core::Store::DEFAULT, + recv: [0u8; RECV_BUF_SIZE], + } + } +} + +struct ByteGather<'a, 'b>(&'a [u8], &'b [u8]); + +impl minicbor::Encode for ByteGather<'_, '_> { + fn encode( + &self, + e: &mut minicbor::Encoder, + _ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + e.bytes_len((self.0.len().wrapping_add(self.1.len())) as u64)?; + e.writer_mut() + .write_all(self.0) + .map_err(minicbor::encode::Error::write)?; + e.writer_mut() + .write_all(self.1) + .map_err(minicbor::encode::Error::write)?; + Ok(()) + } +} + +impl CborLen for ByteGather<'_, '_> { + fn cbor_len(&self, ctx: &mut C) -> usize { + let n = self.0.len().wrapping_add(self.1.len()); + n.cbor_len(ctx).wrapping_add(n) + } +} + +impl From> + for MetadataError +{ + fn from( + _: minicbor::encode::Error, + ) -> MetadataError { + MetadataError::TooLong + } +} + +mod config { + include!(concat!(env!("OUT_DIR"), "/ereport_config.rs")); +} diff --git a/task/packrat/src/main.rs b/task/packrat/src/main.rs index 9adfe64468..0a3e43c39e 100644 --- a/task/packrat/src/main.rs +++ b/task/packrat/src/main.rs @@ -25,17 +25,52 @@ //! functions. //! 3. packrat never calls into any other task, as calling into a task gives the //! callee opportunity to fault the caller. - +//! +//! ## ereport aggregation +//! +//! When the "ereport" feature flag is enabled, packrat is also responsible for +//! aggregating ereports received from other tasks, as described in [RFD 545]. +//! In addition to enabling packrat's "ereport" feature, the RNG driver task +//! must have its "ereport" feature flag enabled, so that it can generate a +//! restart ID and send it to packrat on startup. Otherwise, ereports will never +//! be reported. +//! +//! Other tasks interact with the ereport aggregation subsystem through three +//! IPC operations: +//! +//! - `deliver_ereport`: called by any task which wishes to record an ereport, +//! with a read-only lease containing the CBOR-encoded ereport data. Packrat +//! will store the ereport in its buffer, provided that space remains for the +//! message. +//! +//! - `read_ereports`: called by the `snitch` task, this IPC reads ereports +//! starting at the requested starting ENA into the provided lease. The +//! `committed_ena` parameter indicates that all ereports with ENAs earlier +//! than the provided one have been written to persistent storage, and +//! packrat may flush them from its buffer, to free memory for new +//! ereports. +//! +//! - `set_ereport_restart_id`: called by the `rng` task to set the +//! 128-bit random restart ID that uniquely identifies this system's +//! boot/restart. No ereports will be reported until this IPC has been +//! called. +//! +//! If the "ereport" feature flag is *not* enabled, packrat's `deliver_ereport` +//! and `read_ereports` IPCs will always fail with +//! `ClientError::UnknownOperation`. +//! +//! [RFD 545]: https://rfd.shared.oxide.computer/rfd/0545 #![no_std] #![no_main] use core::convert::Infallible; +use gateway_ereport_messages as ereport_messages; use idol_runtime::{Leased, LenLimit, NotificationHandler, RequestError}; use ringbuf::{ringbuf, ringbuf_entry}; use static_cell::ClaimOnceCell; use task_packrat_api::{ - CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, - VpdIdentity, + CacheGetError, CacheSetError, EreportReadError, HostStartupOptions, + MacAddressBlock, VpdIdentity, }; use userlib::RecvMessage; @@ -56,6 +91,9 @@ use gimlet::SpdData; #[cfg(feature = "cosmo")] use cosmo::SpdData; +#[cfg(feature = "ereport")] +mod ereport; + #[cfg(not(any(feature = "gimlet", feature = "cosmo")))] type SpdData = spd_data::SpdData<0, 0>; // dummy type @@ -66,7 +104,13 @@ enum Trace { MacAddressBlockSet(TraceSet), VpdIdentitySet(TraceSet), SetNextBootHostStartupOptions(HostStartupOptions), - SpdDataUpdate { index: u8, offset: usize, len: u8 }, + SpdDataUpdate { + index: u8, + offset: usize, + len: u8, + }, + #[cfg(feature = "ereport")] + RestartIdSet(TraceSet), } impl From> for Trace { @@ -81,6 +125,21 @@ impl From> for Trace { } } +#[cfg(feature = "ereport")] +impl From> for Trace { + fn from(value: TraceSet) -> Self { + // Turn this into a TraceSet instead of the newtype so that + // Humility formats it in a less ugly way. + Self::RestartIdSet(match value { + TraceSet::Set(id) => TraceSet::Set(id.into()), + TraceSet::SetToSameValue(id) => TraceSet::SetToSameValue(id.into()), + TraceSet::AttemptedSetToNewValue(id) => { + TraceSet::AttemptedSetToNewValue(id.into()) + } + }) + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum TraceSet { // Initial set (always succeeds) @@ -93,7 +152,6 @@ enum TraceSet { } ringbuf!(Trace, 16, Trace::None); - #[export_name = "main"] fn main() -> ! { struct StaticBufs { @@ -103,6 +161,8 @@ fn main() -> ! { gimlet_bufs: gimlet::StaticBufs, #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs, + #[cfg(feature = "ereport")] + ereport_bufs: ereport::EreportBufs, } let StaticBufs { ref mut mac_address_block, @@ -111,6 +171,8 @@ fn main() -> ! { ref mut gimlet_bufs, #[cfg(feature = "cosmo")] ref mut cosmo_bufs, + #[cfg(feature = "ereport")] + ref mut ereport_bufs, } = { static BUFS: ClaimOnceCell = ClaimOnceCell::new(StaticBufs { @@ -120,6 +182,8 @@ fn main() -> ! { gimlet_bufs: gimlet::StaticBufs::new(), #[cfg(feature = "cosmo")] cosmo_bufs: cosmo::StaticBufs::new(), + #[cfg(feature = "ereport")] + ereport_bufs: ereport::EreportBufs::new(), }); BUFS.claim() }; @@ -133,6 +197,8 @@ fn main() -> ! { grapefruit_data: grapefruit::GrapefruitData::new(), #[cfg(feature = "cosmo")] cosmo_data: cosmo::CosmoData::new(cosmo_bufs), + #[cfg(feature = "ereport")] + ereport_store: ereport::EreportStore::new(ereport_bufs), }; let mut buffer = [0; idl::INCOMING_SIZE]; @@ -150,6 +216,8 @@ struct ServerImpl { grapefruit_data: grapefruit::GrapefruitData, #[cfg(feature = "cosmo")] cosmo_data: cosmo::CosmoData, + #[cfg(feature = "ereport")] + ereport_store: ereport::EreportStore, } impl ServerImpl { @@ -408,6 +476,82 @@ impl idl::InOrderPackratImpl for ServerImpl { )) } } + + #[cfg(not(feature = "ereport"))] + fn set_ereport_restart_id( + &mut self, + _: &RecvMessage, + _: u128, + ) -> Result<(), RequestError> { + Ok(()) + } + + #[cfg(feature = "ereport")] + fn set_ereport_restart_id( + &mut self, + _: &RecvMessage, + value: u128, + ) -> Result<(), RequestError> { + let restart_id = ereport_messages::RestartId::new(value); + Self::set_once(&mut self.ereport_store.restart_id, restart_id) + .map_err(Into::into) + } + + #[cfg(not(feature = "ereport"))] + fn deliver_ereport( + &mut self, + _: &RecvMessage, + _: LenLimit, 1024usize>, + ) -> Result<(), RequestError> { + // go away, we don't know how to do that + Err(idol_runtime::ClientError::UnknownOperation.fail()) + } + + #[cfg(feature = "ereport")] + fn deliver_ereport( + &mut self, + msg: &RecvMessage, + data: LenLimit, 1024usize>, + ) -> Result<(), RequestError> { + self.ereport_store.deliver_ereport(msg, data) + } + + #[cfg(not(feature = "ereport"))] + fn read_ereports( + &mut self, + _msg: &RecvMessage, + _: ereport_messages::RequestIdV0, + _: ereport_messages::RestartId, + _: ereport_messages::Ena, + _: u8, + _: ereport_messages::Ena, + _: Leased, + ) -> Result> { + // go away, we don't know how to do that + Err(idol_runtime::ClientError::UnknownOperation.fail()) + } + + #[cfg(feature = "ereport")] + fn read_ereports( + &mut self, + _msg: &RecvMessage, + request_id: ereport_messages::RequestIdV0, + restart_id: ereport_messages::RestartId, + begin_ena: ereport_messages::Ena, + limit: u8, + committed_ena: ereport_messages::Ena, + data: Leased, + ) -> Result> { + self.ereport_store.read_ereports( + request_id, + restart_id, + begin_ena, + limit, + committed_ena, + data, + self.identity.as_ref(), + ) + } } impl NotificationHandler for ServerImpl { @@ -423,8 +567,8 @@ impl NotificationHandler for ServerImpl { mod idl { use super::{ - CacheGetError, CacheSetError, HostStartupOptions, MacAddressBlock, - VpdIdentity, + ereport_messages, CacheGetError, CacheSetError, EreportReadError, + HostStartupOptions, MacAddressBlock, VpdIdentity, }; include!(concat!(env!("OUT_DIR"), "/server_stub.rs")); diff --git a/task/snitch/Cargo.toml b/task/snitch/Cargo.toml new file mode 100644 index 0000000000..a5ed4bdfd2 --- /dev/null +++ b/task/snitch/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "task-snitch" +version = "0.1.0" +edition = "2021" + +[dependencies] +userlib = { path = "../../sys/userlib" } +counters = { path = "../../lib/counters" } +static-cell = { path = "../../lib/static-cell" } +idol-runtime.workspace = true +zerocopy.workspace = true +zerocopy-derive.workspace = true + +gateway-ereport-messages.workspace = true +task-net-api = { path = "../net-api", features = ["use-smoltcp"] } +task-packrat-api = { path = "../packrat-api" } + +[build-dependencies] +build-util = { path = "../../build/util" } +anyhow = { workspace = true } + +[features] +vlan = ["task-net-api/vlan"] + +[[bin]] +name = "task-snitch" +test = false +doctest = false +bench = false + +[lints] +workspace = true diff --git a/task/snitch/build.rs b/task/snitch/build.rs new file mode 100644 index 0000000000..9596a97e79 --- /dev/null +++ b/task/snitch/build.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +fn main() -> anyhow::Result<()> { + build_util::build_notifications()?; + Ok(()) +} diff --git a/task/snitch/src/main.rs b/task/snitch/src/main.rs new file mode 100644 index 0000000000..97f8eff410 --- /dev/null +++ b/task/snitch/src/main.rs @@ -0,0 +1,167 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! the snitch: ereport evacuation +//! +//! The snitch is the component of the ereport subsystem responsible for +//! evacuating ereports over the network, as described in [RFD 545 § 4.4]. The +//! snitch task does not store ereports in its memory; this is `packrat`'s +//! responsibility. Instead, the snitch's role is to receive requests for +//! ereports over the management network, read them from packrat, and forward +//! them to the requesting management gateway. +//! +//! This split is necessary because, in order to communicate over the management +//! network, the snitch must run at a relatively low priority: in particular, it +//! must be lower than that of the `net` task, of which it is a client. Since we +//! would like a variety of tasks to be able to *report* errors through the +//! ereport subsystem, the task responsible for aggregating ereports in memory +//! must run at a high priority, so that as many other tasks as possible may act +//! as clients of it. Furthermore, we would like to include the SP's VPD +//! identity in ereport messages, and this is already stored in packrat. +//! Therefore, we separate the responsibility for storing ereports from the +//! responsibility for sending them over the network. +//! +//! Due to this separation of responsibilities, the snitch task is fairly +//! simple. It receives packets sent to the ereport socket, interprets the +//! request message, and forwards the request to packrat. Any ereports sent back +//! by packrat are sent in response to the request. The snitch ends up being a +//! pretty dumb proxy: as the response packet is encoded by packrat; all we end +//! up doing is taking the bytes received from packrat and stuffing them into +//! the socket's send queue. The real purpose of this thing is just to serve as +//! a trampoline between the high priority level of packrat and a priority level +//! lower than that of the net task. +//! +//! [RFD 545 § 4.4]: https://rfd.shared.oxide.computer/rfd/0545#_evacuation +#![no_std] +#![no_main] + +use counters::{count, counters, Count}; +use gateway_ereport_messages::Request; +use task_net_api::{ + LargePayloadBehavior, Net, RecvError, SendError, SocketName, +}; +use task_packrat_api::Packrat; +use userlib::{sys_recv_notification, task_slot}; +use zerocopy::TryFromBytes; + +task_slot!(NET, net); +task_slot!(PACKRAT, packrat); + +#[derive(Count, Copy, Clone)] +enum Event { + RecvPacket, + RequestRejected, + ReadError(#[count(children)] task_packrat_api::EreportReadError), + Respond, +} + +struct StaticBufs { + rx_buf: [u8; REQ_SZ], + tx_buf: [u8; UDP_PACKET_SZ], +} + +const REQ_SZ: usize = core::mem::size_of::(); +const UDP_PACKET_SZ: usize = 1024; + +counters!(Event); + +#[export_name = "main"] +fn main() -> ! { + let net = Net::from(NET.get_task_id()); + let packrat = Packrat::from(PACKRAT.get_task_id()); + + const SOCKET: SocketName = SocketName::ereport; + + let StaticBufs { + ref mut rx_buf, + ref mut tx_buf, + } = { + static BUFS: static_cell::ClaimOnceCell = + static_cell::ClaimOnceCell::new(StaticBufs { + rx_buf: [0u8; REQ_SZ], + tx_buf: [0u8; UDP_PACKET_SZ], + }); + BUFS.claim() + }; + + loop { + let meta = match net.recv_packet( + SOCKET, + LargePayloadBehavior::Discard, + &mut rx_buf[..], + ) { + Ok(meta) => meta, + Err(RecvError::QueueEmpty) => { + // Our incoming queue is empty. Wait for more packets. + sys_recv_notification(notifications::SOCKET_MASK); + continue; + } + Err(RecvError::ServerRestarted) => { + // `net` restarted; just retry. + continue; + } + }; + + // Okay, we got a packet! + count!(Event::RecvPacket); + let request = + match Request::try_ref_from_bytes(&rx_buf[..meta.size as usize]) { + Ok(req) => req, + Err(_) => { + // We ignore malformatted, truncated, etc. packets. + count!(Event::RequestRejected); + continue; + } + }; + + let size = match request { + Request::V0(req) => match packrat.read_ereports( + req.request_id, + req.restart_id, + req.start_ena, + req.limit, + req.committed_ena() + .copied() + .unwrap_or(gateway_ereport_messages::Ena::NONE), + &mut tx_buf[..], + ) { + Ok(size) => size, + Err(e) => { + // Packrat's mad. Reject the request. + // + // Presently, the only time we'd see an error here is if we + // have yet to generate a restart ID. + count!(Event::ReadError(e)); + continue; + } + }, + }; + + // With the response packet prepared, we may need to attempt + // sending more than once. + loop { + match net.send_packet(SOCKET, meta, &tx_buf[..size]) { + Ok(()) => { + count!(Event::Respond); + break; + } + // If `net` just restarted, immediately retry our send. + Err(SendError::ServerRestarted) => continue, + // If our tx queue is full, wait for space. This is the + // same notification we get for incoming packets, so we + // might spuriously wake up due to an incoming packet + // (which we can't service anyway because we are still + // waiting to respond to a previous request); once we + // finally succeed in sending we'll peel any queued + // packets off our recv queue at the top of our main + // loop. + Err(SendError::QueueFull) => { + sys_recv_notification(notifications::SOCKET_MASK); + } + } + } + } +} + +include!(concat!(env!("OUT_DIR"), "/notifications.rs"));