diff --git a/Cargo.lock b/Cargo.lock index 1bc0afbcf4..30e0e405d3 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]] @@ -1633,8 +1633,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]] @@ -1649,8 +1649,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]] @@ -1662,8 +1662,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]] @@ -1682,8 +1682,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]] @@ -1701,8 +1701,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]] @@ -1727,8 +1727,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]] @@ -1740,8 +1740,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]] @@ -1757,8 +1757,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]] @@ -1785,8 +1785,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1803,8 +1803,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]] @@ -1822,8 +1822,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]] @@ -1847,8 +1847,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]] @@ -1864,8 +1864,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]] @@ -1879,8 +1879,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]] @@ -1897,8 +1897,8 @@ dependencies = [ "userlib", "vsc7448", "vsc85xx", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -1907,8 +1907,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]] @@ -1918,8 +1918,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]] @@ -1931,8 +1931,8 @@ dependencies = [ "idol", "ringbuf", "tlvc", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2001,8 +2001,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]] @@ -2021,8 +2021,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]] @@ -2036,8 +2036,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]] @@ -2061,8 +2061,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]] @@ -2085,8 +2085,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]] @@ -2113,8 +2113,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]] @@ -2142,8 +2142,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2156,8 +2156,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]] @@ -2178,8 +2178,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]] @@ -2191,8 +2191,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]] @@ -2221,8 +2221,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]] @@ -2253,8 +2253,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]] @@ -2265,8 +2265,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2278,8 +2278,8 @@ dependencies = [ "stm32f3", "stm32f4", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2293,8 +2293,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]] @@ -2341,8 +2341,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2360,8 +2360,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]] @@ -2372,8 +2372,8 @@ dependencies = [ "stm32h7", "userlib", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2388,9 +2388,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]] @@ -2401,8 +2402,8 @@ dependencies = [ "ringbuf", "stm32h7", "vcell", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2417,8 +2418,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]] @@ -2448,7 +2449,7 @@ dependencies = [ "stm32h7", "syn 2.0.98", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2475,8 +2476,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]] @@ -2502,8 +2503,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]] @@ -2524,8 +2525,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2543,8 +2544,8 @@ dependencies = [ "stm32g0", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2588,8 +2589,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]] @@ -2606,8 +2607,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]] @@ -2631,8 +2632,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]] @@ -2665,8 +2666,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]] @@ -2683,8 +2684,8 @@ dependencies = [ "ringbuf", "serde", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -2705,8 +2706,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]] @@ -2718,8 +2719,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]] @@ -2736,8 +2737,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]] @@ -2753,8 +2754,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]] @@ -2842,7 +2843,7 @@ name = "endoscope-abi" version = "0.1.0" dependencies = [ "sha3", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -2984,6 +2985,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" @@ -2999,7 +3008,7 @@ dependencies = [ "strum", "strum_macros", "uuid", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3235,8 +3244,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]] @@ -3305,8 +3314,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]] @@ -3362,7 +3371,7 @@ source = "git+https://github.com/oxidecomputer/idolatry.git#6c54f3b87c58f329b07d dependencies = [ "counters", "userlib", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -3485,8 +3494,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]] @@ -3540,8 +3549,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", ] @@ -3605,8 +3614,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]] @@ -3662,8 +3671,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", ] @@ -3694,8 +3703,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]] @@ -3831,6 +3840,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" @@ -3848,7 +3863,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "becf18ac384ecf6f53b2db3b1549eebff664c67ecf259ae99be5912193291686" dependencies = [ - "minicbor", + "minicbor 0.25.1", + "serde", +] + +[[package]] +name = "minicbor-serde" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e45e8beeefea1b8b6f52fa188a5b6ea3746c2885606af8d4d8bf31cee633fb" +dependencies = [ + "minicbor 0.26.4", "serde", ] @@ -4074,7 +4099,7 @@ version = "0.1.0" dependencies = [ "hubpack", "serde", - "zerocopy 0.8.25", + "zerocopy 0.8.26", ] [[package]] @@ -4907,8 +4932,9 @@ dependencies = [ name = "snitch-core" version = "0.1.0" dependencies = [ + "counters", "heapless", - "minicbor-serde", + "minicbor-serde 0.3.2", "serde", "unwrap-lite", ] @@ -5102,8 +5128,8 @@ dependencies = [ "serde", "stm32h7", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5188,8 +5214,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]] @@ -5255,8 +5281,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]] @@ -5273,8 +5299,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5296,8 +5322,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]] @@ -5323,8 +5349,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]] @@ -5343,8 +5369,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]] @@ -5427,8 +5469,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]] @@ -5475,8 +5517,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]] @@ -5491,8 +5533,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]] @@ -5524,8 +5566,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]] @@ -5543,8 +5585,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -5574,8 +5616,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]] @@ -5622,8 +5664,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]] @@ -5645,8 +5687,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]] @@ -5670,35 +5712,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]] @@ -5740,6 +5791,8 @@ dependencies = [ "hubpack", "idol", "idol-runtime", + "minicbor 0.26.4", + "minicbor-serde 0.4.1", "mutable-statics", "num-traits", "paste", @@ -5747,11 +5800,12 @@ dependencies = [ "ringbuf", "serde", "static_assertions", + "task-packrat-api", "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]] @@ -5769,8 +5823,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]] @@ -5793,8 +5847,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]] @@ -5814,8 +5868,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]] @@ -5832,8 +5886,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]] @@ -5885,8 +5956,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]] @@ -5904,8 +5975,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]] @@ -5936,8 +6007,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]] @@ -5961,8 +6032,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]] @@ -5984,8 +6055,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]] @@ -6003,8 +6074,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]] @@ -6024,8 +6095,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]] @@ -6039,8 +6110,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]] @@ -6069,8 +6140,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]] @@ -6083,8 +6154,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]] @@ -6099,8 +6170,8 @@ dependencies = [ "serde", "ssmarshal", "userlib", - "zerocopy 0.8.25", - "zerocopy-derive 0.8.25", + "zerocopy 0.8.26", + "zerocopy-derive 0.8.26", ] [[package]] @@ -6115,8 +6186,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]] @@ -6132,8 +6203,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]] @@ -6153,8 +6224,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]] @@ -6442,8 +6513,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]] @@ -6532,8 +6603,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]] @@ -6921,7 +6992,7 @@ dependencies = [ "toml-task", "toml_edit", "walkdir", - "zerocopy 0.8.25", + "zerocopy 0.8.26", "zip", ] @@ -6947,11 +7018,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]] @@ -6978,9 +7049,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 01689b3ba6..ec0360fda5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,8 @@ 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 } +minicbor-serde = { version = "0.4.0", 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 +146,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 9fdf427294..24f85fbf43 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" @@ -144,7 +154,7 @@ priority = 8 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "cosmo_seq"] +task-slots = ["i2c_driver", "sensor", "cosmo_seq", "packrat"] notifications = ["timer"] [tasks.hiffy] @@ -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 @@ -1517,6 +1538,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.sprot] # ROT_IRQ (af=0 for GPIO, af=15 when EXTI is implemneted) rot_irq = { port = "F", pin = 2, af = 0} # XXX can we use EXTI now? 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 2b092d78ff..76609ffcb2 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" @@ -145,7 +146,7 @@ priority = 6 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "gimlet_seq"] +task-slots = ["i2c_driver", "sensor", "gimlet_seq", "packrat"] notifications = ["timer"] [tasks.hiffy] @@ -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,6 +1336,15 @@ 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 } + [config.sprot] # ROT_IRQ (af=0 for GPIO, af=15 when EXTI is implemneted) rot_irq = { port = "E", pin = 3, af = 0} 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 f26a085701..628a5ad46d 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 @@ -275,5 +300,5 @@ priority = 6 max-sizes = {flash = 65536, ram = 16384 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] 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 ae12b991ea..38fa6eef55 100644 --- a/app/grapefruit/base.toml +++ b/app/grapefruit/base.toml @@ -107,8 +107,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/minibar/app.toml b/app/minibar/app.toml index 57b46fdc47..e653e0f9b9 100644 --- a/app/minibar/app.toml +++ b/app/minibar/app.toml @@ -124,7 +124,7 @@ priority = 6 max-sizes = {flash = 32768, ram = 8192 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor"] +task-slots = ["i2c_driver", "sensor", "packrat"] notifications = ["timer"] [tasks.sensor] diff --git a/app/psc/base.toml b/app/psc/base.toml index 64270506fa..95ad0ddae7 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" @@ -277,7 +286,7 @@ priority = 4 max-sizes = {flash = 32768, ram = 4096} stacksize = 2504 start = true -task-slots = ["i2c_driver", "sensor", "sys"] +task-slots = ["i2c_driver", "sensor", "sys", "packrat"] features = ["psc"] notifications = ["timer"] @@ -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..b3e6bf6daf 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" @@ -293,7 +294,7 @@ priority = 6 max-sizes = {flash = 32768, ram = 8192 } stacksize = 3800 start = true -task-slots = ["i2c_driver", "sensor", "sequencer"] +task-slots = ["i2c_driver", "sensor", "sequencer", "packrat"] notifications = ["timer"] [tasks.validate] @@ -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 8b830e838f..b8df57f85d 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..a4ed286b7a --- /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("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/power/Cargo.toml b/task/power/Cargo.toml index 9c7425c04c..cffd1f122e 100644 --- a/task/power/Cargo.toml +++ b/task/power/Cargo.toml @@ -9,6 +9,8 @@ cortex-m.workspace = true hubpack.workspace = true idol-runtime.workspace = true num-traits.workspace = true +minicbor.workspace = true +minicbor-serde.workspace = true paste.workspace = true pmbus.workspace = true serde.workspace = true @@ -23,6 +25,7 @@ drv-sidecar-seq-api = { path = "../../drv/sidecar-seq-api", optional = true } drv-stm32xx-sys-api = { path = "../../drv/stm32xx-sys-api", features = ["family-stm32h7"], optional = true } mutable-statics = { path = "../../lib/mutable-statics" } ringbuf = { path = "../../lib/ringbuf" } +task-packrat-api = { path = "../packrat-api" } task-power-api = { path = "../power-api" } task-sensor-api = { path = "../sensor-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } diff --git a/task/power/src/bsp/cosmo_a.rs b/task/power/src/bsp/cosmo_a.rs index edec0d859f..c3d3edf4a2 100644 --- a/task/power/src/bsp/cosmo_a.rs +++ b/task/power/src/bsp/cosmo_a.rs @@ -10,11 +10,18 @@ use crate::{ use drv_i2c_devices::{lm5066i::*, max5970::*}; use ringbuf::*; +use task_packrat_api::Packrat; use userlib::units::*; +use crate::ereports; + pub(crate) const CONTROLLER_CONFIG_LEN: usize = 43; const MAX5970_CONFIG_LEN: usize = 22; +/// Minimum threshold for MAX970 12V output error reporting. If the output +/// voltage drops below this threshold, we generate an ereport. +const V12OUT_EREPORT_MIN: f32 = 11.0; + pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ rail_controller!(IBC, bmr491, v12_sys_a2, A2), @@ -53,25 +60,95 @@ pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; ), max5970_controller!(HotSwapIO, v3p3_m2a_a0hp, A0, Ohms(0.003)), max5970_controller!(HotSwapIO, v3p3_m2b_a0hp, A0, Ohms(0.003)), - max5970_controller!(HotSwapIO, v12_u2a_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2a_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2a_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2b_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2b_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2b_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2c_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2c_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2c_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2d_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2d_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2d_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2e_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2e_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2e_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2f_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2f_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2f_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2g_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2g_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2g_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2h_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2h_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2h_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2i_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2i_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2i_a0, A0, Ohms(0.005)), - max5970_controller!(HotSwapIO, v12_u2j_a0, A0, Ohms(0.005), true), + max5970_controller!( + HotSwapIO, + v12_u2j_a0, + A0, + Ohms(0.005), + true, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2j_a0, A0, Ohms(0.005)), ltc4282_controller!(HotSwapIO, v12_mcio_a0hp, A0, Ohms(0.001)), ltc4282_controller!(HotSwapIO, v12_ddr5_abcdef_a0, A0, Ohms(0.001)), @@ -155,13 +232,17 @@ enum Trace { /// Trace record written for each MAX5970. /// - /// The `last_bounce_detected` field and those starting with `crossbounce_` - /// are copied from running state and may not be updated on every trace - /// event. The other fields are read while emitting the trace record and - /// should be current. + /// The `last_bounce_detected` and `vout_sag_began_at` fields, and those + /// starting with `crossbounce_` are copied from running state and may not + /// be updated on every trace event. The other fields are read while + /// emitting the trace record and should be current. Max5970 { sensor: SensorId, last_bounce_detected: Option, + /// If output voltage dipped below the configured minimum threshold, + /// this is the time at which the voltage sag was observed. This value + /// is cleared when output voltage rises above the minimum threshold. + vout_sag_began_at: Option, status0: u8, status1: u8, status3: u8, @@ -188,8 +269,9 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, - sensor: SensorId, + ctrl: &PowerControllerConfig, peaks: &mut Max5970Peaks, + packrat: &mut Packrat, now: u32, ) { let max_vout = match dev.max_vout() { @@ -212,16 +294,46 @@ fn trace_max5970( _ => return, }; + let sensor = ctrl.current; + // TODO: this update should probably happen after all I/O is done. + let mut ereport = None; if peaks.iout.bounced(min_iout, max_iout) || peaks.vout.bounced(min_vout, max_vout) { peaks.last_bounce_detected = Some(now); - } + // Presumably we have power-cycled, so clear the current sag detection + // so that we detect a new one once we've come back. + peaks.vout_sag_began_at = None; + } else if peaks.vout_sag_began_at.is_none() + && min_vout < ctrl.vout_min_threshold + { + // If we are tracking a minimum output voltage threshold on this + // sensor, check if we're below it, and generate an ereport. Once a + // sag is detected, this "latches" until the minimum output voltage + // rises above the threshold again. + // + // We only do this if a bounce is not detected,as we don't want to + // report sags that occur due to the system's power state changing + // intentionally. + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + ctrl.vout_min_threshold, + )); + } else if min_vout >= ctrl.vout_min_threshold { + // If we have gone back above the threshold, clear the current sag so + // that we will report a new one. + peaks.vout_sag_began_at = None; + }; ringbuf_entry!(Trace::Max5970 { sensor, last_bounce_detected: peaks.last_bounce_detected, + vout_sag_began_at: peaks.vout_sag_began_at, status0: match dev.read_reg(Register::status0) { Ok(reg) => reg, _ => return, @@ -255,6 +367,24 @@ fn trace_max5970( crossbounce_min_vout: peaks.vout.crossbounce_min, crossbounce_max_vout: peaks.vout.crossbounce_max, }); + + if let Some(report) = ereport { + deliver_ereport(packrat, &report); + } +} + +// This is in its own function so that we only push a stack frame large enough +// for the ereport buffer if needed. +#[inline(never)] +fn deliver_ereport(packrat: &mut Packrat, report: &impl serde::Serialize) { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } } #[derive(Copy, Clone)] @@ -312,6 +442,7 @@ struct Max5970Peaks { iout: Max5970Peak, vout: Max5970Peak, last_bounce_detected: Option, + vout_sag_began_at: Option, } pub(crate) struct State { @@ -392,6 +523,7 @@ impl State { &mut self, devices: &[Device], state: PowerState, + packrat: &mut Packrat, ) { // // Trace the detailed state every ten seconds, provided that we are in A0. @@ -399,19 +531,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, sensor), peak) in CONTROLLER_CONFIG + for ((dev, ctrl), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.current)) + Some((dev, c)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, sensor, peak, self.fired); + trace_max5970(dev, ctrl, peak, packrat, self.fired); } } diff --git a/task/power/src/bsp/gimlet_bcdef.rs b/task/power/src/bsp/gimlet_bcdef.rs index de81dca8ed..5233f6aa1f 100644 --- a/task/power/src/bsp/gimlet_bcdef.rs +++ b/task/power/src/bsp/gimlet_bcdef.rs @@ -3,17 +3,22 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::{ - i2c_config, i2c_config::sensors, Device, PowerControllerConfig, PowerState, - SensorId, + ereports, i2c_config, i2c_config::sensors, Device, PowerControllerConfig, + PowerState, SensorId, }; use drv_i2c_devices::max5970::*; use ringbuf::*; +use task_packrat_api::Packrat; use userlib::units::*; pub(crate) const CONTROLLER_CONFIG_LEN: usize = 37; const MAX5970_CONFIG_LEN: usize = 22; +/// Minimum threshold for MAX970 12V output error reporting. If the output +/// voltage drops below this threshold, we generate an ereport. +const V12OUT_EREPORT_MIN: f32 = 11.0; + pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; CONTROLLER_CONFIG_LEN] = [ rail_controller!(IBC, bmr491, v12_sys_a2, A2), @@ -33,25 +38,95 @@ pub(crate) static CONTROLLER_CONFIG: [PowerControllerConfig; adm1272_controller!(Fan, v54_fan, A2, Ohms(0.002)), max5970_controller!(HotSwapIO, v3p3_m2a_a0hp, A0, Ohms(0.004)), max5970_controller!(HotSwapIO, v3p3_m2b_a0hp, A0, Ohms(0.004)), - max5970_controller!(HotSwapIO, v12_u2a_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2a_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2a_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2b_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2b_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2b_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2c_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2c_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2c_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2d_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2d_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2d_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2e_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2e_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2e_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2f_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2f_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2f_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2g_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2g_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2g_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2h_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2h_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2h_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2i_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2i_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2i_a0, A0, Ohms(0.008)), - max5970_controller!(HotSwapIO, v12_u2j_a0, A0, Ohms(0.005)), + max5970_controller!( + HotSwapIO, + v12_u2j_a0, + A0, + Ohms(0.005), + false, + V12OUT_EREPORT_MIN + ), max5970_controller!(HotSwapIO, v3p3_u2j_a0, A0, Ohms(0.008)), ]; @@ -112,13 +187,17 @@ enum Trace { /// Trace record written for each MAX5970. /// - /// The `last_bounce_detected` field and those starting with `crossbounce_` - /// are copied from running state and may not be updated on every trace - /// event. The other fields are read while emitting the trace record and - /// should be current. + /// The `last_bounce_detected` and `vout_sag_began_at` fields, and those + /// starting with `crossbounce_` are copied from running state and may not + /// be updated on every trace event. The other fields are read while + /// emitting the trace record and should be current. Max5970 { sensor: SensorId, last_bounce_detected: Option, + /// If output voltage dipped below the configured minimum threshold, + /// this is the time at which the voltage sag was observed. This value + /// is cleared when output voltage rises above the minimum threshold. + vout_sag_began_at: Option, status0: u8, status1: u8, status3: u8, @@ -145,8 +224,9 @@ ringbuf!(Trace, TRACE_DEPTH, Trace::None); /// If any I2C operation fails, this will abort its work and return. fn trace_max5970( dev: &Max5970, - sensor: SensorId, + ctrl: &PowerControllerConfig, peaks: &mut Max5970Peaks, + packrat: &mut Packrat, now: u32, ) { let max_vout = match dev.max_vout() { @@ -169,16 +249,46 @@ fn trace_max5970( _ => return, }; + let sensor = ctrl.current; + // TODO: this update should probably happen after all I/O is done. + let mut ereport = None; if peaks.iout.bounced(min_iout, max_iout) || peaks.vout.bounced(min_vout, max_vout) { peaks.last_bounce_detected = Some(now); - } + // Presumably we have power-cycled, so clear the current sag detection + // so that we detect a new one once we've come back. + peaks.vout_sag_began_at = None; + } else if peaks.vout_sag_began_at.is_none() + && min_vout < ctrl.vout_min_threshold + { + // If we are tracking a minimum output voltage threshold on this + // sensor, check if we're below it, and generate an ereport. Once a + // sag is detected, this "latches" until the minimum output voltage + // rises above the threshold again. + // + // We only do this if a bounce is not detected,as we don't want to + // report sags that occur due to the system's power state changing + // intentionally. + peaks.vout_sag_began_at = Some(now); + ereport = Some(ereports::VoutSag::new( + ctrl.rail, + now, + sensor.into(), + min_vout, + ctrl.vout_min_threshold, + )); + } else if min_vout >= ctrl.vout_min_threshold { + // If we have gone back above the threshold, clear the current sag so + // that we will report a new one. + peaks.vout_sag_began_at = None; + }; ringbuf_entry!(Trace::Max5970 { sensor, last_bounce_detected: peaks.last_bounce_detected, + vout_sag_began_at: peaks.vout_sag_began_at, status0: match dev.read_reg(Register::status0) { Ok(reg) => reg, _ => return, @@ -212,6 +322,24 @@ fn trace_max5970( crossbounce_min_vout: peaks.vout.crossbounce_min, crossbounce_max_vout: peaks.vout.crossbounce_max, }); + + if let Some(report) = ereport { + deliver_ereport(packrat, &report); + } +} + +// This is in its own function so that we only push a stack frame large enough +// for the ereport buffer if needed. +#[inline(never)] +fn deliver_ereport(packrat: &mut Packrat, report: &impl serde::Serialize) { + let mut ereport_buf = [0u8; 128]; + let mut s = minicbor_serde::Serializer::new( + minicbor::encode::write::Cursor::new(&mut ereport_buf[..]), + ); + if report.serialize(&mut s).is_ok() { + let len = s.into_encoder().into_writer().position(); + packrat.deliver_ereport(&ereport_buf[..len]); + } } #[derive(Copy, Clone)] @@ -269,6 +397,7 @@ struct Max5970Peaks { iout: Max5970Peak, vout: Max5970Peak, last_bounce_detected: Option, + vout_sag_began_at: Option, } pub(crate) struct State { @@ -288,6 +417,7 @@ impl State { &mut self, devices: &[Device], state: PowerState, + packrat: &mut Packrat, ) { // // Trace the detailed state every ten seconds, provided that we are in A0. @@ -295,19 +425,19 @@ impl State { if state == PowerState::A0 && self.fired % TRACE_SECONDS == 0 { ringbuf_entry!(Trace::Now(self.fired)); - for ((dev, sensor), peak) in CONTROLLER_CONFIG + for ((dev, ctrl), peak) in CONTROLLER_CONFIG .iter() .zip(devices.iter()) .filter_map(|(c, dev)| { if let Device::Max5970(dev) = dev { - Some((dev, c.current)) + Some((dev, c)) } else { None } }) .zip(self.peaks.iter_mut()) { - trace_max5970(dev, sensor, peak, self.fired); + trace_max5970(dev, ctrl, peak, packrat, self.fired); } } diff --git a/task/power/src/bsp/gimletlet_2.rs b/task/power/src/bsp/gimletlet_2.rs index d0a762faee..d00d8fb859 100644 --- a/task/power/src/bsp/gimletlet_2.rs +++ b/task/power/src/bsp/gimletlet_2.rs @@ -32,6 +32,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/minibar.rs b/task/power/src/bsp/minibar.rs index 7c7bb3af13..81ee1b728d 100644 --- a/task/power/src/bsp/minibar.rs +++ b/task/power/src/bsp/minibar.rs @@ -32,6 +32,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/psc_bc.rs b/task/power/src/bsp/psc_bc.rs index b07edfdf63..c20371c04b 100644 --- a/task/power/src/bsp/psc_bc.rs +++ b/task/power/src/bsp/psc_bc.rs @@ -55,6 +55,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/bsp/sidecar_bcd.rs b/task/power/src/bsp/sidecar_bcd.rs index 0db2b52eb5..acdd15d554 100644 --- a/task/power/src/bsp/sidecar_bcd.rs +++ b/task/power/src/bsp/sidecar_bcd.rs @@ -55,6 +55,7 @@ impl State { &self, _devices: &[crate::Device], _state: PowerState, + _packrat: &mut task_packrat_api::Packrat, ) { } } diff --git a/task/power/src/ereports.rs b/task/power/src/ereports.rs new file mode 100644 index 0000000000..d3b427f3c3 --- /dev/null +++ b/task/power/src/ereports.rs @@ -0,0 +1,39 @@ +// 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/. + +//! Ereport type definitions used by multiple BSPs. + +// Which ereport types are used depends on the board. +#![allow(dead_code)] + +use crate::sensor_api::SensorId; + +#[derive(serde::Serialize)] +pub(crate) struct VoutSag { + k: &'static str, + rail: &'static str, + time: u32, + sensor_id: u32, + vout_min: f32, + threshold: f32, +} + +impl VoutSag { + pub(crate) fn new( + rail: &'static str, + time: u32, + sensor_id: SensorId, + vout_min: f32, + threshold: f32, + ) -> Self { + Self { + k: "pwr.vout_under_threshold", + rail, + time, + sensor_id: sensor_id.into(), + vout_min, + threshold, + } + } +} diff --git a/task/power/src/main.rs b/task/power/src/main.rs index 7a3dbce033..aae7265eb1 100644 --- a/task/power/src/main.rs +++ b/task/power/src/main.rs @@ -23,6 +23,7 @@ use drv_i2c_devices::raa229620a::*; use drv_i2c_devices::tps546b24a::*; use pmbus::Phase; use ringbuf::*; +use task_packrat_api::Packrat; use task_power_api::{ Bmr491Event, PmbusValue, RawPmbusBlock, RenesasBlackbox, MAX_BLOCK_LEN, }; @@ -37,6 +38,8 @@ use drv_i2c_devices::{ VoltageSensor, }; +mod ereports; + #[derive(Copy, Clone, PartialEq)] enum Trace { GotVersion(u32), @@ -57,6 +60,7 @@ enum PowerState { const TIMER_INTERVAL: u32 = 1000; task_slot!(I2C, i2c_driver); +task_slot!(PACKRAT, packrat); task_slot!(SENSOR, sensor); include!(concat!(env!("OUT_DIR"), "/i2c_config.rs")); @@ -104,6 +108,17 @@ struct PowerControllerConfig { input_current: Option, temperature: Option, phases: Option<&'static [u8]>, + // May or may not be used, depending on BSP. + #[allow(dead_code)] + rail: &'static str, + /// Minimum threshold for MAX5970 output voltage sag error reporting. If + /// output voltage drops below this value, an ereport is generated. + /// + /// Note that `f32::MIN` is equivalent to "don't monitor for voltage sag on this rail". + /// + /// May or may not be used, depending on BSP. + #[allow(dead_code)] + vout_min_threshold: f32, } /// Bound device, which exposes sensor functions @@ -320,6 +335,8 @@ macro_rules! rail_controller { sensors::[<$dev:upper _ $rail:upper _TEMPERATURE_SENSOR>] ), phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], + rail: stringify!($rail:upper), + vout_min_threshold: f32::MAX, } } }; @@ -340,6 +357,8 @@ macro_rules! rail_controller_notemp { input_current: None, temperature: None, phases: i2c_config::pmbus::[<$dev:upper _ $rail:upper _PHASES>], + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -362,6 +381,8 @@ macro_rules! adm1272_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -382,6 +403,8 @@ macro_rules! ltc4282_controller { input_current: None, temperature: None, phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -404,6 +427,8 @@ macro_rules! lm5066_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -426,6 +451,8 @@ macro_rules! lm5066i_controller { sensors::[] ), phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -434,9 +461,9 @@ macro_rules! lm5066i_controller { #[allow(unused_macros)] macro_rules! max5970_controller { ($which:ident, $rail:ident, $state:ident, $rsense:expr) => { - max5970_controller!($which, $rail, $state, $rsense, false) + max5970_controller!($which, $rail, $state, $rsense, false, f32::MIN) }; - ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr) => { + ($which:ident, $rail:ident, $state:ident, $rsense:expr, $avg:expr, $vout_min_threshold:expr) => { paste::paste! { PowerControllerConfig { state: $crate::PowerState::$state, @@ -449,6 +476,8 @@ macro_rules! max5970_controller { input_current: None, temperature: None, phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: $vout_min_threshold, } } }; @@ -474,6 +503,8 @@ macro_rules! mwocp68_controller { temperature: None, // Temperature sensors are independent of // power rails and measured separately phases: None, + rail: stringify!($rail:upper), + vout_min_threshold: f32::MIN, } } }; @@ -520,6 +551,7 @@ fn main() -> ! { sensor: sensor_api::Sensor::from(SENSOR.get_task_id()), devices: claim_devices(i2c_task), bsp: bsp::State::init(), + packrat: Packrat::from(PACKRAT.get_task_id()), }; let mut buffer = [0; idl::INCOMING_SIZE]; @@ -534,6 +566,7 @@ struct ServerImpl { sensor: sensor_api::Sensor, devices: &'static mut [Device; bsp::CONTROLLER_CONFIG_LEN], bsp: bsp::State, + packrat: Packrat, } impl ServerImpl { @@ -608,7 +641,8 @@ impl ServerImpl { } } - self.bsp.handle_timer_fired(self.devices, state); + self.bsp + .handle_timer_fired(self.devices, state, &mut self.packrat); } /// Find the BMR491 and return an `I2cDevice` handle 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"));