diff --git a/.github/buildomat/common.sh b/.github/buildomat/common.sh index f8df2081..8cdee6c2 100644 --- a/.github/buildomat/common.sh +++ b/.github/buildomat/common.sh @@ -11,9 +11,9 @@ TOFINO_STAGES=20 # These describe which version of the SDE to download and where to find it -SDE_COMMIT=2a6b33211c9675996dcb99fe939045506667ae94 -SDE_PKG_SHA256=d32739c368d1666b98dd74e25e22f83c209982e2c6670de6db5d6fdf49b5e275 -SDE_DEB_SHA256=3ecbf7c677bb722b351d5af74cee44fab70c1bb5eadc6ab2558ba714a8c3978b +SDE_COMMIT=6107bba19c2ae4da870ef6f8dfcff61ca457eb88 +SDE_PKG_SHA256=ca4ecfd8cfca4d53caa8e91747db6c2754aa71dd89788239c2a6faa4321204b6 +SDE_DEB_SHA256=c34158295489cb4ea91d94a840aa384bb364298b4c1eb49124bced0438893d6e [ `uname -s` == "SunOS" ] && SERIES=illumos [ `uname -s` == "SunOS" ] || SERIES=linux diff --git a/.helix/languages.toml b/.helix/languages.toml index d831603b..775e3fde 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -4,4 +4,4 @@ name = "rust" # default to tofino_sde, can change this to any other feature such as softnpu # as needed during development but in source control this should probably # remain as tofino_sde -features = [ "tofino_asic" ] +features = [ "interrupts", "tofino_asic" ] diff --git a/Cargo.lock b/Cargo.lock index d47d1424..4056647b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,16 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +dependencies = [ + "anstyle", + "unicode-width 0.2.1", +] + [[package]] name = "anstream" version = "1.0.0" @@ -175,6 +185,7 @@ dependencies = [ "chrono", "common 0.1.0", "dpd-types", + "intr", "lazy_static", "libc", "oximeter", @@ -190,7 +201,7 @@ dependencies = [ "softnpu 0.2.0 (git+https://github.com/oxidecomputer/softnpu?branch=main)", "strum 0.27.2", "thiserror 1.0.69", - "tofino 0.1.0 (git+https://github.com/oxidecomputer/tofino?branch=main)", + "tofino 0.1.0 (git+https://github.com/oxidecomputer/tofino?branch=atype)", "tokio", "transceiver-controller", "uuid", @@ -469,6 +480,24 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitset" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/bitset?branch=main#11eb609ae2f02ec2cb116f84210b5655b26924a1" +dependencies = [ + "bitset-macro", + "seq-macro", +] + +[[package]] +name = "bitset-macro" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/bitset?branch=main#11eb609ae2f02ec2cb116f84210b5655b26924a1" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "bitstruct" version = "0.1.1" @@ -1056,6 +1085,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.18.1" @@ -1470,7 +1508,7 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.1", @@ -3118,7 +3156,7 @@ dependencies = [ "itertools 0.14.0", "libc", "macaddr", - "nix", + "nix 0.30.1", "omicron-common", "omicron-uuid-kinds", "omicron-workspace-hack", @@ -3280,6 +3318,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16" +[[package]] +name = "intr" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen", + "bitset", + "cc", + "clap", + "libc", + "regs", + "rust_rpi", + "slog", + "slog-async", + "slog-term", + "strum 0.27.2", + "thiserror 1.0.69", + "tofino 0.1.0 (git+https://github.com/oxidecomputer/tofino?branch=atype)", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -3470,9 +3528,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdlpi-sys" @@ -4063,6 +4121,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -5821,6 +5891,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "regs" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/tofino?branch=atype#e31a85ead919f0aacd3907cc65896c4cc83e7479" +dependencies = [ + "anyhow", + "bitset", + "cc", + "rsf", + "rust_rpi", +] + [[package]] name = "reqwest" version = "0.12.23" @@ -5947,6 +6029,38 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsf" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/rsf?branch=associated_types#f99d66f091f3e20bdb93f2a55dcce32e361c3a33" +dependencies = [ + "annotate-snippets", + "anyhow", + "bitset", + "bitset-macro", + "camino", + "camino-tempfile", + "clap", + "colored", + "convert_case 0.8.0", + "expectorate", + "iddqd", + "num_enum 0.7.5", + "petgraph 0.8.2", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", + "thiserror 2.0.18", + "winnow 0.7.14", +] + +[[package]] +name = "rust_rpi" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/rsf?branch=associated_types#f99d66f091f3e20bdb93f2a55dcce32e361c3a33" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -6296,6 +6410,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.228" @@ -6844,7 +6964,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.117", @@ -7457,11 +7577,13 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tofino" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/tofino?branch=main#d13f3e2dbe59ff8deb2b73156665ae3035f53da1" +source = "git+https://github.com/oxidecomputer/tofino?branch=atype#e2762712165e6e07b2ddaea35023053d4858cd6d" dependencies = [ "anyhow", "cc", "illumos-devinfo", + "libc", + "nix 0.31.3", ] [[package]] @@ -7868,7 +7990,7 @@ dependencies = [ "clap", "hubpack", "itertools 0.14.0", - "nix", + "nix 0.30.1", "schemars 0.8.22", "serde", "slog", diff --git a/Cargo.toml b/Cargo.toml index 362dee8e..262ad33c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,13 @@ members = [ "dpd-types", "dpd-types/versions", "dropshot-apis", + "intr", "packet", "pcap", "swadm", "tfportd", "uplinkd", - "xtask", + "xtask", "intr", ] resolver = "2" @@ -38,11 +39,12 @@ dpd-client = { path = "dpd-client" } dpd-types = { path = "dpd-types" } dpd-types-versions = { path = "dpd-types/versions" } common = { path = "common" } +intr = { path = "intr" } packet = { path = "packet" } pcap = { path = "pcap" } # oxide dependencies from github -scuffle = { git = "https://github.com/oxidecomputer/scuffle", version = "0.1.0", features = ["smf-by-instance"] } +bitset = { git = "https://github.com/oxidecomputer/bitset", branch = "main"} internal-dns-resolver = { git = "https://github.com/oxidecomputer/omicron", branch = "main" } internal-dns-types = { git = "https://github.com/oxidecomputer/omicron", branch = "main" } ispf = { git = "https://github.com/oxidecomputer/ispf" } @@ -56,14 +58,17 @@ oximeter-producer = { git = "https://github.com/oxidecomputer/omicron", branch = oximeter-instruments = { git = "https://github.com/oxidecomputer/omicron", branch = "main", default-features = false, features = ["kstat"] } oxnet = { version = "0.1.4", default-features = false, features = ["schemars", "serde"] } propolis = { git = "https://github.com/oxidecomputer/propolis" } +regs = { git = "https://github.com/oxidecomputer/tofino", branch = "atype" } +rust_rpi = { git = "https://github.com/oxidecomputer/rsf", branch = "associated_types" } +scuffle = { git = "https://github.com/oxidecomputer/scuffle", version = "0.1.0", features = ["smf-by-instance"] } smf = { git = "https://github.com/illumos/smf-rs" } softnpu-lib = { git = "https://github.com/oxidecomputer/softnpu" , package = "softnpu" , branch = "main"} -tofino = { git = "https://github.com/oxidecomputer/tofino", branch = "main" } +tofino = { git = "https://github.com/oxidecomputer/tofino", branch = "atype" } transceiver-controller = { git = "https://github.com/oxidecomputer/transceiver-control", branch = "main" } # public dependencies from crates.io anyhow = "1.0" -bytes = "1.6" +bytes = "1.11" camino = { version = "1.1", features = ["serde1"] } cfg-if = "1" chrono = "0.4" diff --git a/asic/Cargo.toml b/asic/Cargo.toml index 96d42164..86f521f3 100644 --- a/asic/Cargo.toml +++ b/asic/Cargo.toml @@ -14,7 +14,6 @@ tofino_stub = [] softnpu = ["softnpu-lib", "dep:propolis"] chaos = [] multicast = ["aal/multicast", "common/multicast"] -interrupts = [] [lib] # The genpd.rs code generated by bindgen causes the doctest to fail @@ -24,6 +23,7 @@ doctest = false aal.workspace = true common.workspace = true dpd-types.workspace = true +intr.workspace = true propolis = { workspace = true, optional = true , features = ["falcon"] } tofino = { workspace = true, optional = true } diff --git a/asic/src/chaos/mod.rs b/asic/src/chaos/mod.rs index e5d58831..015b6a17 100644 --- a/asic/src/chaos/mod.rs +++ b/asic/src/chaos/mod.rs @@ -371,7 +371,6 @@ macro_rules! unfurl { $handle.config.$name.unfurled(&$handle.log, stringify!($name))? }; } -pub(crate) use unfurl; /// A convenience macro for unfurling multicast chaos. The $name should be a /// regular `Chaos` member of [`AsicMulticastConfigConfig`]. The `handle` is diff --git a/asic/src/tofino_asic/c/bf_wrapper.c b/asic/src/tofino_asic/c/bf_wrapper.c index 6dd568ad..a2e9598a 100644 --- a/asic/src/tofino_asic/c/bf_wrapper.c +++ b/asic/src/tofino_asic/c/bf_wrapper.c @@ -143,10 +143,11 @@ bfw_init_ctx(const char *devpath, const char *p4_dir, } ctx->conf_file = conffile; ctx->init_mode = BF_DEV_INIT_COLD; - ctx->running_in_background = 1;; + ctx->running_in_background = 1; if (devpath != NULL) { ctx->kernel_pkt = 1; + ctx->skip_interrupt_thread = true; } else { perror("failed to find a tofino device"); } diff --git a/asic/src/tofino_asic/imported_bf_functions b/asic/src/tofino_asic/imported_bf_functions index 547abe30..67f3a4f4 100644 --- a/asic/src/tofino_asic/imported_bf_functions +++ b/asic/src/tofino_asic/imported_bf_functions @@ -185,10 +185,6 @@ bf_snapshot_entry_params_get bf_snapshot_handle_get # Interrupt management -lld_enable_all_ints -lld_dump_new_ints -lld_int_poll -bf_err_interrupt_handling_mode_set pipe_mgr_is_device_locked pipe_mgr_tcam_scrub_timer_set pipe_mgr_tcam_scrub_timer_get diff --git a/asic/src/tofino_asic/interrupt_monitor.rs b/asic/src/tofino_asic/interrupt_monitor.rs index 096e79ae..9ae68fba 100644 --- a/asic/src/tofino_asic/interrupt_monitor.rs +++ b/asic/src/tofino_asic/interrupt_monitor.rs @@ -7,20 +7,30 @@ use std::{thread::sleep, time::Duration}; use crate::tofino_asic::{ - BF_INVALID_ARG, BF_SUCCESS, + BF_SUCCESS, bf_wrapper::bf_error_str, genpd::{ - bf_dev_id_t, bf_err_interrupt_handling_mode_set, bf_subdev_id_t, - lld_dump_new_ints, lld_enable_all_ints, lld_int_poll, - pipe_mgr_is_device_locked, pipe_mgr_tcam_scrub_timer_set, + bf_dev_id_t, pipe_mgr_is_device_locked, pipe_mgr_tcam_scrub_timer_set, }, }; use slog::{Logger, info, warn}; const DEV_ID: bf_dev_id_t = 0; -const SUBDEV_ID: bf_subdev_id_t = 0; const INTERVAL: Duration = Duration::from_secs(5); +// Interrupt monitoring requires a sufficiently new tofino driver. +pub fn interrupts_supported() -> aal::AsicResult { + let v = tofino::get_driver_version("/dev/tofino/1").map_err(|e| { + aal::AsicError::Synthetic(format!( + "unable to get driver version: {e:?}" + )) + })?; + if v.major > 1 || v.minor >= 2 { + return Ok(true); + } + Ok(false) +} + /// Monitoring interrupts requires a number of precursory steps to set things up /// in the SDE and on the ASIC. The `monitor_interrupts` function is designed as /// a state machine that will drive forward toward the termial state of actively @@ -44,10 +54,12 @@ pub fn monitor_interrupts(log: Logger) -> ! { wait_for_unlock(&log); enable_tcam_scrub(&log); - set_interrupt_handling_mode(&log); - enable_lld_interrupts(&log); - monitoring_loop(&log) + loop { + if let Err(e) = intr::interrupt_monitor(&log) { + slog::error!(log, "interrupt monitor failed: {e:?}"); + } + } } fn wait_for_unlock(log: &Logger) { @@ -79,53 +91,3 @@ fn enable_tcam_scrub(log: &Logger) { sleep(INTERVAL) } } - -fn set_interrupt_handling_mode(log: &Logger) { - loop { - let rc = unsafe { bf_err_interrupt_handling_mode_set(DEV_ID, true) }; - if rc == BF_SUCCESS { - info!(log, "interrupt handling mode set"); - break; - } - warn!( - log, - "failed to set interrupt handling mode"; - "error" => bf_error_str(rc) - ); - sleep(INTERVAL); - } -} - -fn enable_lld_interrupts(log: &Logger) { - loop { - let rc = unsafe { lld_enable_all_ints(DEV_ID, SUBDEV_ID) }; - if rc == BF_SUCCESS { - info!(log, "enabled lld interrupts"); - break; - } - warn!( - log, - "failed to enable lld interrupts"; - "error" => bf_error_str(rc) - ); - sleep(INTERVAL); - } -} - -fn monitoring_loop(log: &Logger) -> ! { - loop { - let rc = unsafe { lld_int_poll(DEV_ID, SUBDEV_ID, true) }; - if rc != BF_SUCCESS { - warn!(log, "lld_int_poll: {}", bf_error_str(rc)); - } - - let rc = unsafe { lld_dump_new_ints(DEV_ID, SUBDEV_ID) }; - // BF_INVALID_ARG means interrupts were found and dumped (yes, really) - // BF_SUCCESS means no new interrupts - // Anything else is an actual error - if rc != BF_SUCCESS && rc != BF_INVALID_ARG { - warn!(log, "lld_dump_new_ints: {}", bf_error_str(rc)); - } - sleep(INTERVAL); - } -} diff --git a/asic/src/tofino_asic/mod.rs b/asic/src/tofino_asic/mod.rs index 18eb3eed..8d44c486 100644 --- a/asic/src/tofino_asic/mod.rs +++ b/asic/src/tofino_asic/mod.rs @@ -17,7 +17,6 @@ use common::ports::*; mod bf_wrapper; mod genpd; -#[cfg(feature = "interrupts")] pub mod interrupt_monitor; mod link_fsm; #[cfg(feature = "multicast")] diff --git a/asic/src/tofino_asic/shadow_interrupt_list b/asic/src/tofino_asic/shadow_interrupt_list new file mode 100644 index 00000000..08ca2fce --- /dev/null +++ b/asic/src/tofino_asic/shadow_interrupt_list @@ -0,0 +1,249 @@ +This is a mapping of each bit in the shadow interrupt register on a Tofino 2 to +the interrupt, or interrupts, that will cause the shadow bit to be set. + +This list is derived from the JBay_Interrupt_List.csv file in the tofino SDE. +In some cases, the CSV identifies a range of bits that are associated with a +set of interrupts. It's not clear whether each interrupt trigger will cause the +full range of bits to be set, an arbitrary bit in the set, or a deterministic +bit in the set. They parse this list using a python script, and then generate +10s of thousands of lines of macro-laden code. + +As best as I can tell, this generated code only looks at the first bit in the +range to determine if any of the interrupts need to be examined. According to +their comments, the CSV file originated with their hardware team. It's possible +that the hardware team designed this mechanism to allow software to implement a +virtual mux, so multiple threads could each monitor a subset of the interrupts, +and the software team didn't take advantage of that. + +Bit Interrupt status register +--- ------------------------------------------------------------ + 32 device_select.tbc.* + 33 device_select.tbc.* + 64 device_select.lfltr[0].ctrl.intr_stat + 65 device_select.lfltr[0].ctrl.intr_stat + 66 device_select.lfltr[1].ctrl.intr_stat + 67 device_select.lfltr[1].ctrl.intr_stat + 68 device_select.lfltr[2].ctrl.intr_stat + 69 device_select.lfltr[2].ctrl.intr_stat + 70 device_select.lfltr[3].ctrl.intr_stat + 71 device_select.lfltr[3].ctrl.intr_stat + 72 device_select.tm_top.tm_wac_top.wac_pipe[*].* + 73 device_select.tm_top.tm_wac_top.wac_pipe[*].* + 74 device_select.tm_top.tm_caa_top.intr.stat + 75 device_select.tm_top.tm_caa_top.intr.stat + 76 device_select.tm_top.tm_qac_top.qac_pipe[*].* + 77 device_select.tm_top.tm_qac_top.qac_pipe[*].* + 78 device_select.tm_top.tm_scha_top.sch[*].intr.stat + 79 device_select.tm_top.tm_scha_top.sch[*].intr.stat + 80 device_select.tm_top.tm_schb_top.sch[*].intr.stat + 81 device_select.tm_top.tm_schb_top.sch[*].intr.stat + 82 device_select.tm_top.tm_clc_top.clc[*].intr.stat + 83 device_select.tm_top.tm_clc_top.clc[*].intr.stat + 84 device_select.tm_top.tm_pex_top.pex[*].intr.stat + 85 device_select.tm_top.tm_pex_top.pex[*].intr.stat + 86 device_select.tm_top.tm_qlc_top.qlc[*].intr.stat + 87 device_select.tm_top.tm_qlc_top.qlc[*].intr.stat + 88 device_select.tm_top.tm_prc_top.prc[*].intr.stat + 89 device_select.tm_top.tm_prc_top.prc[*].intr.stat + 90 device_select.tm_top.tm_pre_top.pre[*].intr.stat + 91 device_select.tm_top.tm_pre_top.pre[*].intr.stat + 92 device_select.tm_top.tm_psc_top.psc_common.intr.stat + 92 device_select.tm_top.tm_psc_top.psc[*].intr.stat + 93 device_select.tm_top.tm_psc_top.psc_common.intr.stat + 93 device_select.tm_top.tm_psc_top.psc[*].intr.stat + 94 device_select.cbc.cbc_cbus.* + 95 device_select.cbc.cbc_cbus.* + 96 device_select.cbc.cbc_cbus.* + 97 device_select.cbc.cbc_cbus.* +256 pipes[0].mau[0].* +257 pipes[0].mau[0].* +258 pipes[0].mau[1].* +259 pipes[0].mau[1].* +260 pipes[0].mau[2].* +261 pipes[0].mau[2].* +262 pipes[0].mau[3].* +263 pipes[0].mau[3].* +264 pipes[0].mau[4].* +265 pipes[0].mau[4].* +266 pipes[0].mau[5].* +267 pipes[0].mau[5].* +268 pipes[0].mau[6].* +269 pipes[0].mau[6].* +270 pipes[0].mau[7].* +271 pipes[0].mau[7].* +272 pipes[0].mau[8].* +273 pipes[0].mau[8].* +274 pipes[0].mau[9].* +275 pipes[0].mau[9].* +276 pipes[0].mau[10].* +277 pipes[0].mau[10].* +278 pipes[0].mau[11].* +279 pipes[0].mau[11].* +280 pipes[0].mau[12].* +281 pipes[0].mau[12].* +282 pipes[0].mau[13].* +283 pipes[0].mau[13].* +284 pipes[0].mau[14].* +285 pipes[0].mau[14].* +286 pipes[0].mau[15].* +287 pipes[0].mau[15].* +288 pipes[0].mau[16].* +289 pipes[0].mau[16].* +290 pipes[0].mau[17].* +291 pipes[0].mau[17].* +292 pipes[0].mau[18].* +293 pipes[0].mau[18].* +294 pipes[0].mau[19].* +295 pipes[0].mau[19].* +296 pipes[0].pardereg.pgstnreg.* +297 pipes[0].pardereg.pgstnreg.* +298 pipes[0].pardereg.dprsrreg.* +298 pipes[0].pardereg.mirreg.* +299 pipes[0].pardereg.dprsrreg.* +299 pipes[0].pardereg.mirreg.* +320 pipes[1].mau[0].* +321 pipes[1].mau[0].* +322 pipes[1].mau[1].* +323 pipes[1].mau[1].* +324 pipes[1].mau[2].* +325 pipes[1].mau[2].* +326 pipes[1].mau[3].* +327 pipes[1].mau[3].* +328 pipes[1].mau[4].* +329 pipes[1].mau[4].* +330 pipes[1].mau[5].* +331 pipes[1].mau[5].* +332 pipes[1].mau[6].* +333 pipes[1].mau[6].* +334 pipes[1].mau[7].* +335 pipes[1].mau[7].* +336 pipes[1].mau[8].* +337 pipes[1].mau[8].* +338 pipes[1].mau[9].* +339 pipes[1].mau[9].* +340 pipes[1].mau[10].* +341 pipes[1].mau[10].* +342 pipes[1].mau[11].* +343 pipes[1].mau[11].* +344 pipes[1].mau[12].* +345 pipes[1].mau[12].* +346 pipes[1].mau[13].* +347 pipes[1].mau[13].* +348 pipes[1].mau[14].* +349 pipes[1].mau[14].* +350 pipes[1].mau[15].* +351 pipes[1].mau[15].* +352 pipes[1].mau[16].* +353 pipes[1].mau[16].* +354 pipes[1].mau[17].* +355 pipes[1].mau[17].* +356 pipes[1].mau[18].* +357 pipes[1].mau[18].* +358 pipes[1].mau[19].* +359 pipes[1].mau[19].* +360 pipes[1].pardereg.pgstnreg.* +361 pipes[1].pardereg.pgstnreg.* +362 pipes[1].pardereg.dprsrreg.* +362 pipes[1].pardereg.mirreg.* +363 pipes[1].pardereg.dprsrreg.* +363 pipes[1].pardereg.mirreg.* +384 pipes[2].mau[0].* +385 pipes[2].mau[0].* +386 pipes[2].mau[1].* +387 pipes[2].mau[1].* +388 pipes[2].mau[2].* +389 pipes[2].mau[2].* +390 pipes[2].mau[3].* +391 pipes[2].mau[3].* +392 pipes[2].mau[4].* +393 pipes[2].mau[4].* +394 pipes[2].mau[5].* +395 pipes[2].mau[5].* +396 pipes[2].mau[6].* +397 pipes[2].mau[6].* +398 pipes[2].mau[7].* +399 pipes[2].mau[7].* +400 pipes[2].mau[8].* +401 pipes[2].mau[8].* +402 pipes[2].mau[9].* +403 pipes[2].mau[9].* +404 pipes[2].mau[10].* +405 pipes[2].mau[10].* +406 pipes[2].mau[11].* +407 pipes[2].mau[11].* +408 pipes[2].mau[12].* +409 pipes[2].mau[12].* +410 pipes[2].mau[13].* +411 pipes[2].mau[13].* +412 pipes[2].mau[14].* +413 pipes[2].mau[14].* +414 pipes[2].mau[15].* +415 pipes[2].mau[15].* +416 pipes[2].mau[16].* +417 pipes[2].mau[16].* +418 pipes[2].mau[17].* +419 pipes[2].mau[17].* +420 pipes[2].mau[18].* +421 pipes[2].mau[18].* +422 pipes[2].mau[19].* +423 pipes[2].mau[19].* +424 pipes[2].pardereg.pgstnreg.* +425 pipes[2].pardereg.pgstnreg.* +426 pipes[2].pardereg.dprsrreg.* +426 pipes[2].pardereg.mirreg.* +427 pipes[2].pardereg.dprsrreg.* +427 pipes[2].pardereg.mirreg.* +448 pipes[3].mau[0].* +449 pipes[3].mau[0].* +450 pipes[3].mau[1].* +451 pipes[3].mau[1].* +452 pipes[3].mau[2].* +453 pipes[3].mau[2].* +454 pipes[3].mau[3].* +455 pipes[3].mau[3].* +456 pipes[3].mau[4].* +457 pipes[3].mau[4].* +458 pipes[3].mau[5].* +459 pipes[3].mau[5].* +460 pipes[3].mau[6].* +461 pipes[3].mau[6].* +462 pipes[3].mau[7].* +463 pipes[3].mau[7].* +464 pipes[3].mau[8].* +465 pipes[3].mau[8].* +466 pipes[3].mau[9].* +467 pipes[3].mau[9].* +468 pipes[3].mau[10].* +469 pipes[3].mau[10].* +470 pipes[3].mau[11].* +471 pipes[3].mau[11].* +472 pipes[3].mau[12].* +473 pipes[3].mau[12].* +474 pipes[3].mau[13].* +475 pipes[3].mau[13].* +476 pipes[3].mau[14].* +477 pipes[3].mau[14].* +478 pipes[3].mau[15].* +479 pipes[3].mau[15].* +480 pipes[3].mau[16].* +481 pipes[3].mau[16].* +482 pipes[3].mau[17].* +483 pipes[3].mau[17].* +484 pipes[3].mau[18].* +485 pipes[3].mau[18].* +486 pipes[3].mau[19].* +487 pipes[3].mau[19].* +488 pipes[3].pardereg.pgstnreg.* +489 pipes[3].pardereg.pgstnreg.* +490 pipes[3].pardereg.dprsrreg.* +481 pipes[3].pardereg.mirreg.* +492 pipes[3].pardereg.dprsrreg.* +493 pipes[3].pardereg.mirreg.* +504 device_select.pbc.* +505 device_select.pbc.* +506 device_select.pbc.* +507 device_select.pbc.* +508 device_select.pbc.* +509 device_select.pbc.* +510 device_select.pbc.* +511 device_select.pbc.* diff --git a/dpd/Cargo.toml b/dpd/Cargo.toml index b733dbdb..7ec3b6bc 100644 --- a/dpd/Cargo.toml +++ b/dpd/Cargo.toml @@ -12,7 +12,6 @@ softnpu = ["asic/softnpu"] chaos = ["asic/chaos"] transceiver-chaos = ["dep:rand"] tokio-console = ["dep:console-subscriber", "tokio/tracing"] -interrupts = ["asic/interrupts"] [build-dependencies] anyhow = "1.0" diff --git a/dpd/src/main.rs b/dpd/src/main.rs index a55df82d..9573a987 100644 --- a/dpd/src/main.rs +++ b/dpd/src/main.rs @@ -290,9 +290,10 @@ impl Switch { let mac_mgmt = Mutex::new(macaddrs::MacManagement::new(&log)); #[cfg(feature = "tofino_asic")] - if !asic_hdl.is_model() { - #[cfg(feature = "interrupts")] - run_interrupt_monitor(log.clone()); + if !asic_hdl.is_model() + && let Err(e) = run_interrupt_monitor(log.clone()) + { + error!(log, "failed to start interrupt monitor: {e:?}") } let ws_log = log.new(slog::o!("unit" => "workflow_server")); @@ -754,8 +755,8 @@ async fn sidecar_main(mut switch: Switch) -> anyhow::Result<()> { let rear_links: BTreeMap = switch .switch_ports .ports - .iter() - .filter_map(|(port_id, _)| { + .keys() + .filter_map(|port_id| { if matches!(port_id, PortId::Rear(_)) { let create = LinkCreate { speed: common::ports::PortSpeed::Speed100G, @@ -833,9 +834,15 @@ async fn run_dpd(opt: Opt) -> anyhow::Result<()> { } } -#[cfg(all(feature = "tofino_asic", feature = "interrupts"))] -fn run_interrupt_monitor(log: slog::Logger) { +#[cfg(feature = "tofino_asic")] +fn run_interrupt_monitor(log: slog::Logger) -> anyhow::Result<()> { + // If the driver doesn't support interrupt monitoring, fail gracefully. + if !asic::tofino_asic::interrupt_monitor::interrupts_supported()? { + info!(log, "tofino driver too old to monitor interrupts"); + return Ok(()); + }; std::thread::spawn(move || { asic::tofino_asic::interrupt_monitor::monitor_interrupts(log); }); + Ok(()) } diff --git a/dpd/src/route.rs b/dpd/src/route.rs index df50344a..1108c713 100644 --- a/dpd/src/route.rs +++ b/dpd/src/route.rs @@ -447,6 +447,7 @@ fn replace_route_targets( // Insert all the entries into the table let mut idx = new_entry.index; + #[allow(clippy::explicit_counter_loop)] for target in targets { if let Err(e) = match target.route.tgt_ip { IpAddr::V4(tgt_ip) => table::route_ipv4::add_route_target( diff --git a/dpd/src/types.rs b/dpd/src/types.rs index 2626b8d7..a938a994 100644 --- a/dpd/src/types.rs +++ b/dpd/src/types.rs @@ -12,7 +12,6 @@ use common::SmfError; use common::ports::PortId; use common::ports::QsfpPort; use dpd_types::link::LinkId; -use slog::error; use std::{convert, net::IpAddr}; use transceiver_controller::Error as TransceiverError; diff --git a/intr/Cargo.toml b/intr/Cargo.toml new file mode 100644 index 00000000..40d915d4 --- /dev/null +++ b/intr/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "intr" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow.workspace = true +bitset.workspace = true +clap.workspace = true +libc.workspace = true +regs.workspace = true +rust_rpi.workspace = true +slog.workspace = true +slog-term.workspace = true +slog-async.workspace = true +strum.workspace = true +thiserror.workspace = true +tofino.workspace = true + +[build-dependencies] +cc = "1.2" +bindgen = "0.72" +anyhow = "1.0" diff --git a/intr/src/lib.rs b/intr/src/lib.rs new file mode 100644 index 00000000..0aae2fc9 --- /dev/null +++ b/intr/src/lib.rs @@ -0,0 +1,408 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] + +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::collections::HashSet; +use std::fmt; + +use slog::{debug, error}; + +use rust_rpi::Platform; +use rust_rpi::RegisterInstance; + +mod pcie; +mod tcam; + +const POLL_TIMEOUT_MS: u64 = 100; + +pub type IntrResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum IntrError { + #[error("I/O error: {0:?}")] + Io(std::io::Error), + #[error("ASIC error: {0:?}")] + Asic(String), + #[error("Internal error: {0}")] + Internal(String), + #[error("RPI range error: {0}")] + RpiRange(rust_rpi::OutOfRange), + #[error("{0:?}")] + Other(anyhow::Error), +} + +impl From for IntrError { + fn from(value: rust_rpi::OutOfRange) -> Self { + IntrError::RpiRange(value) + } +} + +impl From for IntrError { + fn from(value: anyhow::Error) -> Self { + IntrError::Other(value) + } +} + +trait Interrupt: fmt::Display { + fn set_enable(&mut self, raw: &mut u32, val: bool); + fn process( + &mut self, + tf: &Tofino, + log: &slog::Logger, + status_raw: u32, + ) -> IntrResult; +} +pub struct Tofino { + pub rpi: regs::Client, + pub pci: tofino::pci::Pci, +} + +impl Tofino { + pub fn new() -> IntrResult { + const DRIVER_PATH: &str = "/dev/tofino/1"; + + let v = tofino::get_driver_version(DRIVER_PATH)?; + if v.major <= 1 && v.minor < 2 { + return Err(IntrError::Asic("tofino driver too old".to_string())); + } + + let pci = tofino::pci::Pci::new(DRIVER_PATH, tofino::REGISTER_SIZE) + .map_err(IntrError::from)?; + let rpi = regs::Client::default(); + Ok(Tofino { rpi, pci }) + } + + pub fn read_register(&self, addr: u32) -> IntrResult { + self.pci.read4(addr).map_err(|e| IntrError::Asic(format!("{e:?}"))) + } + + pub fn write_register(&self, addr: u32, val: u32) -> IntrResult<()> { + self.pci + .write4(addr, val) + .map_err(|e| IntrError::Asic(format!("{e:?}"))) + } +} + +impl Platform for Tofino { + type Error = IntrError; + + fn read>(&self, addr: u32) -> IntrResult { + self.pci + .read4(addr) + .map_err(|e| IntrError::Asic(e.to_string())) + .map(|r| r.into()) + } + + fn write>( + &self, + addr: u32, + value: T, + ) -> IntrResult<()> { + self.pci + .write4(addr, value.into()) + .map_err(|e| IntrError::Asic(e.to_string())) + } +} + +struct ShadowInterrupt { + pub mask: [u32; 16], + shadow_inst: Vec, + shadow_mask_inst: Vec, +} + +#[allow(unused)] +impl ShadowInterrupt { + pub fn new(tf: &Tofino) -> Self { + let ds = tf.rpi.device_select(); + let shadow_inst: Vec = (0u32..16u32) + .map(|idx| { + ds.pcie_bar_01_regs() + .shadow_int(idx) + .expect("we know there are 16 copies of this register") + }) + .collect(); + + let shadow_mask_inst: Vec = (0u32..16u32) + .map(|idx| { + ds.pcie_bar_01_regs() + .shadow_msk(idx) + .expect("we know there are 16 copies of this register") + }) + .collect(); + ShadowInterrupt { mask: [0u32; 16], shadow_inst, shadow_mask_inst } + } + + fn bit_to_idx(bit: u32) -> IntrResult<(usize, u32)> { + if bit > 512 { + Err(IntrError::Internal("bit out of range".to_string())) + } else { + Ok(((bit >> 5) as usize, bit & 0x1f)) + } + } + + pub fn read_shadow_interrupts( + &mut self, + tf: &Tofino, + log: &slog::Logger, + ) -> IntrResult> { + let mut set = HashSet::new(); + for (i, inst) in self.shadow_inst.iter().enumerate() { + let word: u32 = inst + .read(tf) + .map_err(|e| { + IntrError::Asic(format!("failed to read shadow {i}: {e:?}")) + })? + .into(); + if word != 0 { + for bit in 0..32 { + if word & (1 << bit) != 0 { + let shadow = (i << 5) + bit; + set.insert(shadow as u32); + } + } + } + } + Ok(set) + } + + pub fn write_mask(&mut self, tf: &Tofino) -> IntrResult<()> { + for (i, inst) in self.shadow_mask_inst.iter().enumerate() { + inst.write(tf, self.mask[i].into()).map_err(|e| { + IntrError::Asic(format!( + "failed to write shadow mask {i}: {e:?}" + )) + })?; + } + Ok(()) + } + + pub fn read_mask(&mut self, tf: &Tofino) -> IntrResult<()> { + for (i, inst) in self.shadow_mask_inst.iter().enumerate() { + self.mask[i] = u32::from(inst.read(tf).map_err(|e| { + IntrError::Asic(format!( + "failed to write shadow mask {i}: {e:?}" + )) + })?); + } + Ok(()) + } + + pub fn mask_all(&mut self) { + for word in self.mask.iter_mut() { + *word = 0xffffffff; + } + } + + pub fn set_mask_bit(&mut self, bit: u32) -> IntrResult<()> { + let (byte, bit) = Self::bit_to_idx(bit)?; + self.mask[byte] |= 1 << bit; + Ok(()) + } + + pub fn clear_mask_bit(&mut self, bit: u32) -> IntrResult<()> { + let (byte, bit) = Self::bit_to_idx(bit)?; + self.mask[byte] &= !(1 << bit); + Ok(()) + } +} + +// An InterruptGroup represents all of the interrupts whose status is found in +// the same register. When an interrupt is triggered by the ASIC, it will +// update this register and set a bit in the global "shadow interrupt" map. +// While it would be handy if there was a one-to-one correspondence between +// status registers and shadow bits, there is not. In some cases, each bit in +// a register has its own shadow bit. In others, a single shadow bit may be +// set when multiple different interrupts are triggered. To handle both cases, +// each InterruptGroup contains a HashSet containing all of the shadow bits +// that interact with these interrupts. +struct InterruptGroup { + pub name: String, + pub shadow_bits: HashSet, + pub status: u32, + pub enable: u32, + pub interrupts: Vec>, + enable_reg_addr: u32, + status_reg_addr: u32, +} + +#[allow(unused)] +impl InterruptGroup { + pub fn new( + name: impl ToString, + shadows: Vec, + enable_reg: impl RegisterInstance, + status_reg: impl RegisterInstance, + interrupts: Vec>, + ) -> Self { + InterruptGroup { + name: name.to_string(), + shadow_bits: shadows.into_iter().collect(), + enable_reg_addr: enable_reg.addr(), + status_reg_addr: status_reg.addr(), + status: 0, + enable: 0, + interrupts, + } + } + + pub fn read_status(&mut self, tf: &Tofino) -> IntrResult<()> { + self.status = tf.read_register(self.status_reg_addr)?; + Ok(()) + } + + pub fn write_status( + &self, + tf: &Tofino, + log: &slog::Logger, + ) -> IntrResult<()> { + tf.write_register(self.status_reg_addr, self.status) + } + pub fn read_enable(&mut self, tf: &Tofino) -> IntrResult<()> { + self.enable = tf.read_register(self.enable_reg_addr)?; + Ok(()) + } + pub fn write_enable( + &self, + tf: &Tofino, + log: &slog::Logger, + ) -> IntrResult<()> { + tf.write_register(self.enable_reg_addr, self.enable) + } + + pub fn enable_interrupts(&mut self) { + for interrupt in &mut self.interrupts { + interrupt.set_enable(&mut self.enable, true) + } + } + + pub fn disable_interrupts(&mut self) { + for interrupt in &mut self.interrupts { + interrupt.set_enable(&mut self.enable, false) + } + } + + pub fn process_interrupts(&mut self, tf: &Tofino, log: &slog::Logger) { + let stat = self.status; + for i in &mut self.interrupts { + match i.process(tf, log, stat) { + Ok(true) => { + debug!(log, "handled interrupt {i}"); + } + Ok(false) => {} + Err(e) => error!(log, "failed to handle {i}: {e:?}"), + } + } + if let Err(e) = self.write_status(tf, log) { + error!(log, "failed to push status-clearing write: {e:?}"); + } + } +} + +fn build_interrupt_groups(tf: &Tofino) -> Vec { + let mut groups = tcam::groups(tf); + let pg = pcie::groups(tf); + groups.extend(pg); + groups +} + +fn enable_interrupts( + tf: &Tofino, + log: &slog::Logger, + groups: &mut [InterruptGroup], +) -> IntrResult<()> { + for group in groups.iter_mut() { + if let Err(e) = group.read_status(tf) { + error!( + log, + "failed to read interrupt status for {}: {:?}", group.name, e + ); + continue; + } + + group.enable_interrupts(); + if let Err(e) = group.write_enable(tf, log) { + return Err(IntrError::Asic(format!( + "failed to write to enable register: {e:?}" + ))); + } + group.process_interrupts(tf, log); + } + Ok(()) +} + +fn wait_for_interrupts( + tf: &mut Tofino, + log: &slog::Logger, + shadow: &mut ShadowInterrupt, +) -> HashSet { + let timeout = std::time::Duration::from_millis(POLL_TIMEOUT_MS); + loop { + match tf.pci.poll(timeout) { + Err(e) => { + error!(log, "poll of tofino failed: {e:?}"); + } + Ok(false) => {} + Ok(true) => { + if let Err(e) = tf.pci.read_interrupt_count() { + error!( + log, + "failed to read interrupt state from kernel: {e:?}" + ); + continue; + } + + match shadow.read_shadow_interrupts(tf, log) { + Ok(s) => return s, + Err(e) => { + error!(log, "failed to read shadow interrupts: {e:?}") + } + } + } + } + } +} + +pub fn interrupt_monitor(log: &slog::Logger) -> IntrResult<()> { + let mut tf = match Tofino::new() { + Ok(t) => t, + Err(e) => { + panic!("Failed to initialize Tofino interface: {e:?}"); + } + }; + + let mut groups = build_interrupt_groups(&tf); + enable_interrupts(&tf, log, &mut groups)?; + + let mut shadow = ShadowInterrupt::new(&tf); + loop { + let shadows = wait_for_interrupts(&mut tf, log, &mut shadow); + + for group in groups.iter_mut() { + if !shadows.is_disjoint(&group.shadow_bits) { + if let Err(e) = group.read_status(&tf) { + error!( + log, + "Failed to read status register for {}: {:?}", + group.name, + e + ); + continue; + } + if group.status != 0 { + debug!( + log, + " group stat for shadow {}: {:x}", + group.name, + group.status + ); + group.process_interrupts(&tf, log); + } + } + } + } +} diff --git a/intr/src/main.rs b/intr/src/main.rs new file mode 100644 index 00000000..1fa7cbd9 --- /dev/null +++ b/intr/src/main.rs @@ -0,0 +1,356 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] + +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::str::FromStr; + +use anyhow::bail; +use bitset::BitSet; +use clap::{Parser, Subcommand}; +use slog::{Drain, o}; + +use rust_rpi::RegisterInstance; + +#[derive(Debug, Parser)] +pub struct Cli { + #[command(subcommand)] + cmd: CliCommand, +} + +#[derive(Debug, Subcommand)] +enum InjectSubcommand { + #[command(subcommand)] + Tcam(Tcam), + #[command(subcommand)] + Pcie(Pcie), +} + +#[derive(Debug, Subcommand)] +enum CliCommand { + /// Monitor interrupt activity + Monitor, + /// Inject a specific error, triggering the associated interrupt + #[command(subcommand)] + Inject(InjectSubcommand), +} + +/// Interrupts related to TCAM errors on a specific pipe and MAU. +#[derive(Debug, Subcommand)] +pub enum Tcam { + Ecc { + #[clap(long, short = 'p')] + pipe: u32, + #[clap(long, short = 'm')] + mau: u32, + #[clap(long, short = 'r')] + row: u32, + #[clap(long, short = 'a')] + addr: u32, + }, + Channel, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PcieEccTgt { + /// RX request buffer + RxReq, + /// RX completion buffer + RxCpl, + // TX buffer + TxBuf, + // MSI-X memory + Msix, +} + +impl FromStr for PcieEccTgt { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "rxreq" => PcieEccTgt::RxReq, + "rxcpl" => PcieEccTgt::RxCpl, + "txbuf" => PcieEccTgt::TxBuf, + "msix" => PcieEccTgt::Msix, + _ => bail!("invalid PCIe ecc target"), + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PcieBusTgt { + Pbus, + Tbus, + Cbus, + Mbus, +} +impl FromStr for PcieBusTgt { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s.to_lowercase().as_str() { + "pbus" => PcieBusTgt::Pbus, + "tbus" => PcieBusTgt::Tbus, + "cbus" => PcieBusTgt::Cbus, + "mbus" => PcieBusTgt::Mbus, + _ => bail!("invalid PCIe bus target"), + }) + } +} +/// Interrupts delivered on the top-level PCI register +#[derive(Debug, Subcommand)] +pub enum Pcie { + /// Inject a PCI space ECC error + Ecc { + /// Identify the target memory for the error: rxreq, rxcpl, txbuf, msix + #[clap(long, short = 't')] + tgt: PcieEccTgt, + /// The error should be correctible + #[clap(long, short = 'c')] + correctable: bool, + /// The address of the error + #[clap(long, short = 'a')] + addr: u32, + }, + #[clap(visible_alias = "bo")] + BusOverflow { + /// Identify which bus fifo overflow to inject + #[clap(long, short = 'b')] + bus: PcieBusTgt, + /// Is this a posted or non-posted fifo + #[clap(long, short = 'p')] + posted: bool, + }, + /// overflow in DMA Response fifo + #[clap(visible_alias = "do")] + DmaOverflow, + #[clap(visible_alias = "dt")] + /// DMA request timeout + DmaTimeout, + #[clap(visible_alias = "ct")] + /// CPU request timeout + CpuTimeout, + /// PCIe controller link down + #[clap(visible_alias = "ld")] + LinkDown, +} + +macro_rules! validate_arg { + ($v:ident, $max:expr) => { + if $v > $max { + bail!(format!( + "Invalid value for {}: {}. Must be less than {}.", + stringify!($v), + $v, + $max + )) + } + }; +} + +// Inject a TCAM ecc error. The pipe and mau arguments determine which +// registers need to be written to inject the error. The row argument +// determines which bit gets set in the injection register. The address gets +// written to the "error log" register. +fn inject_tcam_ecc_err( + pipe: u32, + mau: u32, + row: u32, + addr: u32, +) -> anyhow::Result<()> { + validate_arg!(pipe, 3); + validate_arg!(mau, 19); + validate_arg!(row, 11); + validate_arg!(addr, 1024); + + let tf = intr::Tofino::new()?; + let tcam = tf.rpi.pipes(pipe).unwrap().mau(mau).unwrap().tcams(); + + let inject_inst = tcam.intr_inject_mau_tcam_array(); + let mut inject_reg = inject_inst.cons(); + + let sbe_inst = tcam.tcam_sbe_errlog(row).unwrap(); + let mut sbe_reg = sbe_inst.cons(); + + inject_reg.set_tcam_sbe((1u32 << row).try_into().unwrap()); + sbe_reg.set_tcam_sbe_errlog_addr(addr.try_into().unwrap()); + println!("writing sbe reg: {:?} at 0x{:x}", sbe_reg, sbe_inst.addr()); + println!( + "writing inject reg {:?} at 0x{:x}", + inject_reg, + inject_inst.addr() + ); + sbe_inst.write(&tf, sbe_reg)?; + inject_inst.write(&tf, inject_reg)?; + Ok(()) +} + +// Inject a PCIe ecc error. +fn inject_pcie_ecc_err( + tgt: PcieEccTgt, + correctible: bool, + addr: u32, +) -> anyhow::Result<()> { + validate_arg!(addr, 1 << 6); + + let true_b = BitSet::<1>::from(true); + let tf = intr::Tofino::new()?; + let pcie_inst = tf.rpi.device_select().pcie_bar_01_regs(); + let intr_inst = pcie_inst.pcie_intr(); + let inject_inst = intr_inst.inj(); + let mut inject_reg = inject_inst.cons(); + + let log_addr = match tgt { + PcieEccTgt::RxReq => match correctible { + true => { + inject_reg.set_rxreqbuf_ecc_sngl(true_b); + pcie_inst.rxbuf_sbe_err_log().addr + } + false => { + inject_reg.set_rxreqbuf_ecc_dual(true_b); + pcie_inst.rxbuf_mbe_err_log().addr + } + }, + PcieEccTgt::RxCpl => match correctible { + true => { + inject_reg.set_rxcplbuf_ecc_sngl(true_b); + pcie_inst.rxcpl_sbe_err_log().addr + } + false => { + inject_reg.set_rxcplbuf_ecc_dual(true_b); + pcie_inst.rxcpl_mbe_err_log().addr + } + }, + PcieEccTgt::TxBuf => match correctible { + true => { + inject_reg.set_txbuf_ecc_sngl(true_b); + pcie_inst.txbuf_sbe_err_log().addr + } + false => { + inject_reg.set_txbuf_ecc_dual(true_b); + pcie_inst.txbuf_mbe_err_log().addr + } + }, + PcieEccTgt::Msix => match correctible { + true => { + inject_reg.set_msix_ecc_sngl(true_b); + pcie_inst.msix_sbe_err_log().addr + } + false => { + inject_reg.set_msix_ecc_dual(true_b); + pcie_inst.msix_mbe_err_log().addr + } + }, + }; + println!( + "writing ECC address 0x{addr:x} to log register at 0x{log_addr:x}" + ); + tf.pci.write4(log_addr, addr)?; + println!( + "writing pci injection register {:?} at 0x{:x}", + inject_reg, + inject_inst.addr() + ); + inject_inst.write(&tf, inject_reg)?; + Ok(()) +} + +fn inject_pcie_bus_overflow( + bus: PcieBusTgt, + posted: bool, +) -> anyhow::Result<()> { + let true_b = BitSet::<1>::from(true); + let tf = intr::Tofino::new()?; + let pcie_inst = tf.rpi.device_select().pcie_bar_01_regs(); + let intr_inst = pcie_inst.pcie_intr(); + let inject_inst = intr_inst.inj(); + let mut inject_reg = inject_inst.cons(); + + match (bus, posted) { + (PcieBusTgt::Pbus, true) => inject_reg.set_pdma_pst_ovf(true_b), + (PcieBusTgt::Pbus, false) => inject_reg.set_pdma_non_ovf(true_b), + (PcieBusTgt::Tbus, true) => inject_reg.set_tdma_pst_ovf(true_b), + (PcieBusTgt::Tbus, false) => inject_reg.set_tdma_non_ovf(true_b), + (PcieBusTgt::Cbus, true) => inject_reg.set_cdma_pst_ovf(true_b), + (PcieBusTgt::Cbus, false) => inject_reg.set_cdma_non_ovf(true_b), + (PcieBusTgt::Mbus, true) => inject_reg.set_mdma_pst_ovf(true_b), + (PcieBusTgt::Mbus, false) => inject_reg.set_mdma_non_ovf(true_b), + } + println!( + "writing pci injection register {:?} at 0x{:x}", + inject_reg, + inject_inst.addr() + ); + inject_inst.write(&tf, inject_reg)?; + Ok(()) +} + +fn inject_pcie_misc(cmd: Pcie) -> anyhow::Result<()> { + let true_b = BitSet::<1>::from(true); + let tf = intr::Tofino::new()?; + let pcie_inst = tf.rpi.device_select().pcie_bar_01_regs(); + let intr_inst = pcie_inst.pcie_intr(); + let inject_inst = intr_inst.inj(); + let mut inject_reg = inject_inst.cons(); + + match cmd { + Pcie::DmaOverflow => inject_reg.set_dma_cpl_ovf(true_b), + Pcie::DmaTimeout => inject_reg.set_dma_timeout(true_b), + Pcie::CpuTimeout => inject_reg.set_cpu_timeout(true_b), + Pcie::LinkDown => inject_reg.set_pcie_linkdown(true_b), + _ => panic!("can't happen"), + } + println!( + "writing pci injection register {:?} at 0x{:x}", + inject_reg, + inject_inst.addr() + ); + inject_inst.write(&tf, inject_reg)?; + Ok(()) +} + +fn log_init() -> anyhow::Result { + let drain = { + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::FullFormat::new(decorator).build().fuse(); + slog_async::Async::new(drain).chan_size(32768).build().fuse() + }; + Ok(slog::Logger::root(drain, o!())) +} + +pub fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let log = log_init()?; + + match cli.cmd { + CliCommand::Monitor => intr::interrupt_monitor(&log)?, + CliCommand::Inject(cmd) => match cmd { + InjectSubcommand::Tcam(tcam) => match tcam { + Tcam::Channel => { + bail!("Channel error injection not yet supported"); + } + Tcam::Ecc { pipe, mau, row, addr } => { + inject_tcam_ecc_err(pipe, mau, row, addr)? + } + }, + InjectSubcommand::Pcie(pcie) => match pcie { + Pcie::Ecc { tgt, correctable, addr } => { + inject_pcie_ecc_err(tgt, correctable, addr)? + } + Pcie::BusOverflow { bus, posted } => { + inject_pcie_bus_overflow(bus, posted)? + } + Pcie::DmaOverflow + | Pcie::DmaTimeout + | Pcie::CpuTimeout + | Pcie::LinkDown => inject_pcie_misc(pcie)?, + }, + }, + } + Ok(()) +} diff --git a/intr/src/pcie.rs b/intr/src/pcie.rs new file mode 100644 index 00000000..fb9040b3 --- /dev/null +++ b/intr/src/pcie.rs @@ -0,0 +1,287 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::fmt; + +use rust_rpi::RegisterInstance; +use slog::error; + +use super::{Interrupt, InterruptGroup, IntrResult, Tofino}; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum EccLocation { + RxReq, + RxCpl, + TxBuf, + Msix, +} + +impl fmt::Display for EccLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EccLocation::RxReq => write!(f, "RX request buffer"), + EccLocation::RxCpl => write!(f, "RX completion buffer"), + EccLocation::TxBuf => write!(f, "TX buffer"), + EccLocation::Msix => write!(f, "MSI-X memory"), + } + } +} + +// This function is called for both single-bit (correctable) and multi-bit +// (uncorrectable) errors. For now we just log them. If/when this gets +// plumbed into FMA, we will presumably want to handle them differently. In +// particular, an uncorrectable error will likely trigger a reset. +fn handle_ecc_error( + log: &slog::Logger, + sbe: bool, + loc: EccLocation, + addr: u32, +) -> IntrResult<()> { + // The address at which the error was detected is stored in the lower 5 bits. + let addr = addr & 0x1f; + let kind = if sbe { "Correctable" } else { "Uncorrectable" }; + error!(log, "{kind} ECC error in {loc} at 0x{addr:x}"); + Ok(()) +} + +// Macro to generate a wrapper around handle_ecc_error for each of the +// different error types. +macro_rules! ecc { + ($name:ident, + $correctible:literal, + $what:expr, + $log_reg:ident + ) => { + fn $name(tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + let pcie_block = tf.rpi.device_select().pcie_bar_01_regs(); + let addr = pcie_block.$log_reg().read_raw(tf)?; + handle_ecc_error(log, $correctible, $what, addr) + } + }; +} + +// macro invocations to generate handlers for each ECC error type +ecc!(handle_rxreqbuf_ecc_dual, false, EccLocation::RxReq, rxbuf_mbe_err_log); +ecc!(handle_rxcplbuf_ecc_dual, false, EccLocation::RxCpl, rxcpl_mbe_err_log); +ecc!(handle_txbuf_ecc_dual, false, EccLocation::TxBuf, txbuf_mbe_err_log); +ecc!(handle_msix_ecc_dual, false, EccLocation::Msix, msix_mbe_err_log); +ecc!(handle_rxreqbuf_ecc_sgl, true, EccLocation::RxReq, rxbuf_sbe_err_log); +ecc!(handle_rxcplbuf_ecc_sgl, true, EccLocation::RxCpl, rxcpl_sbe_err_log); +ecc!(handle_txbuf_ecc_sgl, true, EccLocation::TxBuf, txbuf_sbe_err_log); +ecc!(handle_msix_ecc_sgl, true, EccLocation::Msix, msix_sbe_err_log); + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Bus { + Pbus, + Tbus, + Cbus, + Mbus, +} +impl fmt::Display for Bus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Bus::Pbus => write!(f, "Pbus"), + Bus::Tbus => write!(f, "Tbus"), + Bus::Cbus => write!(f, "Cbus"), + Bus::Mbus => write!(f, "Mbus"), + } + } +} +// This function is called for an overflow in a DMA FIFO. +fn handle_fifo_overflow( + log: &slog::Logger, + bus: Bus, + posted: bool, +) -> IntrResult<()> { + let kind = if posted { "posted" } else { "non-posted" }; + error!(log, "Overflow in DMA {kind} fifo for {bus}"); + Ok(()) +} + +// Macro to generate a wrapper around handle_fifo_overflow for each of the +// different bus and fifo types. +macro_rules! fifo { + ($name:ident, + $bus:expr, + $posted:literal + ) => { + fn $name(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + handle_fifo_overflow(log, $bus, $posted) + } + }; +} + +fifo!(handle_pdma_pst_ovf, Bus::Pbus, true); +fifo!(handle_pdma_non_ovf, Bus::Pbus, false); +fifo!(handle_cdma_pst_ovf, Bus::Cbus, true); +fifo!(handle_cdma_non_ovf, Bus::Cbus, false); +fifo!(handle_tdma_pst_ovf, Bus::Tbus, true); +fifo!(handle_tdma_non_ovf, Bus::Tbus, false); +fifo!(handle_mdma_pst_ovf, Bus::Mbus, true); +fifo!(handle_mdma_non_ovf, Bus::Mbus, false); + +fn handle_dma_timeout(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + error!(log, "DMA request timeout detected"); + Ok(()) +} + +fn handle_cpu_timeout(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + error!(log, "CPU request timeout detected"); + Ok(()) +} + +fn handle_cpu_non_dw_txn(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + error!(log, "PCIe received a request which is non-DW aligned"); + Ok(()) +} + +fn handle_cpu_dw_overflow(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + error!( + log, + "PCIe received a request which has more DW than maximum programmed" + ); + Ok(()) +} + +fn handle_dma_completion_ovf( + _tf: &Tofino, + log: &slog::Logger, +) -> IntrResult<()> { + error!(log, "Overflow detected in DMA response fifo"); + Ok(()) +} + +fn handle_pci_link_down(_tf: &Tofino, log: &slog::Logger) -> IntrResult<()> { + error!( + log, + " PCIe controller link went down without triggering a core reset" + ); + Ok(()) +} + +// macro to generate an Interrupt implementation for each of the interrupts +// managed by the top-level PCIe interrupt register. +macro_rules! interrupt { + ($name:ident, + $bit:literal, + $process:ident + ) => { + #[derive(Debug)] + struct $name; + + impl $name { + pub fn shadow_bit() -> u32 { + $bit as u32 + } + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", stringify!($name)) + } + } + + impl Interrupt for $name { + fn process( + &mut self, + tf: &Tofino, + log: &slog::Logger, + status_raw: u32, + ) -> IntrResult { + if status_raw & 1u32 << $bit != 0 { + $process(tf, log)?; + Ok(true) + } else { + Ok(false) + } + } + + fn set_enable(&mut self, ena_raw: &mut u32, v: bool) { + let mask = 1u32 << $bit; + if v { + *ena_raw |= mask; + } else { + *ena_raw &= !mask; + } + } + } + }; +} + +// Interrupt name, bit in the status register, handler +interrupt!(PcieRxReqBufEccDual, 0, handle_rxreqbuf_ecc_dual); +interrupt!(PcieRxCplBufEccDual, 1, handle_rxcplbuf_ecc_dual); +interrupt!(PcieTxBufEccDual, 2, handle_txbuf_ecc_dual); +interrupt!(PcieMsixEccDual, 3, handle_msix_ecc_dual); +interrupt!(PcieDmaTimeout, 4, handle_dma_timeout); +interrupt!(PcieCpuTimeout, 5, handle_cpu_timeout); +interrupt!(PcieCpuNonDW, 6, handle_cpu_non_dw_txn); +interrupt!(PcieCpuMaxnDW, 7, handle_cpu_dw_overflow); +interrupt!(PcieRxReqBufEccSgl, 8, handle_rxreqbuf_ecc_sgl); +interrupt!(PcieRxCplBufEccSgl, 9, handle_rxcplbuf_ecc_sgl); +interrupt!(PcieTxBufEccSgl, 10, handle_txbuf_ecc_sgl); +interrupt!(PcieMsixEccSgl, 11, handle_msix_ecc_sgl); +interrupt!(PciePdmaPstOvf, 12, handle_pdma_pst_ovf); +interrupt!(PciePdmaNonOvf, 13, handle_pdma_non_ovf); +interrupt!(PcieCdmaPstOvf, 14, handle_cdma_pst_ovf); +interrupt!(PcieCdmaNonOvf, 15, handle_cdma_non_ovf); +interrupt!(PcieTdmaPstOvf, 16, handle_tdma_pst_ovf); +interrupt!(PcieTdmaNonOvf, 17, handle_tdma_non_ovf); +interrupt!(PcieMdmaPstOvf, 18, handle_mdma_pst_ovf); +interrupt!(PcieMdmaNonOvf, 19, handle_mdma_non_ovf); +interrupt!(PcieDmaCplOvf, 20, handle_dma_completion_ovf); +interrupt!(PcieLinkDown, 21, handle_pci_link_down); + +// This macro adds an interrupt to the list of interrupts in the group, and it +// adds the interrupts shadow ID to the vector of shadow IDs to monitor. Note: +// for the interrupts in this group, the shadow IDs are conveniently identical +// to the bit index in the register. +macro_rules! add_interrupt { + ($name:ident, + $shadow_ints:ident, + $int_vec:ident + ) => { + $shadow_ints.push($name::shadow_bit()); + $int_vec.push(Box::new($name)); + }; +} + +pub fn groups(tf: &Tofino) -> Vec { + let pcie_block = tf.rpi.device_select().pcie_bar_01_regs().pcie_intr(); + let mut shadow_ints = Vec::new(); + let mut interrupts: Vec> = Vec::new(); + + add_interrupt!(PcieRxReqBufEccDual, shadow_ints, interrupts); + add_interrupt!(PcieRxCplBufEccDual, shadow_ints, interrupts); + add_interrupt!(PcieTxBufEccDual, shadow_ints, interrupts); + add_interrupt!(PcieMsixEccDual, shadow_ints, interrupts); + add_interrupt!(PcieRxReqBufEccSgl, shadow_ints, interrupts); + add_interrupt!(PcieRxCplBufEccSgl, shadow_ints, interrupts); + add_interrupt!(PcieTxBufEccSgl, shadow_ints, interrupts); + add_interrupt!(PcieMsixEccSgl, shadow_ints, interrupts); + add_interrupt!(PcieDmaTimeout, shadow_ints, interrupts); + add_interrupt!(PcieCpuTimeout, shadow_ints, interrupts); + add_interrupt!(PcieCpuNonDW, shadow_ints, interrupts); + add_interrupt!(PcieCpuMaxnDW, shadow_ints, interrupts); + add_interrupt!(PciePdmaPstOvf, shadow_ints, interrupts); + add_interrupt!(PciePdmaNonOvf, shadow_ints, interrupts); + add_interrupt!(PcieCdmaPstOvf, shadow_ints, interrupts); + add_interrupt!(PcieCdmaNonOvf, shadow_ints, interrupts); + add_interrupt!(PcieTdmaPstOvf, shadow_ints, interrupts); + add_interrupt!(PcieTdmaNonOvf, shadow_ints, interrupts); + add_interrupt!(PcieMdmaPstOvf, shadow_ints, interrupts); + add_interrupt!(PcieMdmaNonOvf, shadow_ints, interrupts); + add_interrupt!(PcieDmaCplOvf, shadow_ints, interrupts); + add_interrupt!(PcieLinkDown, shadow_ints, interrupts); + let group = InterruptGroup::new( + "PCIe Errors".to_string(), + shadow_ints, + pcie_block.en_0(), + pcie_block.stat(), + interrupts, + ); + vec![group] +} diff --git a/intr/src/tcam.rs b/intr/src/tcam.rs new file mode 100644 index 00000000..c1ee6769 --- /dev/null +++ b/intr/src/tcam.rs @@ -0,0 +1,162 @@ +// 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/ +// +// Copyright 2026 Oxide Computer Company + +use std::fmt; + +use bitset::BitSet; +use regs::IntrEnable0MauTcamArray; +use regs::IntrStatusMauTcamArray; +use rust_rpi::RegisterInstance; +use slog::error; + +use super::{Interrupt, InterruptGroup, IntrResult, Tofino}; + +// The RPI knows how many instances there are. It would be handy if it +// provided an API to let us ask. +const CHANNEL_PAIRS: u8 = 4; +const TCAM_ROWS: u8 = 11; + +pub struct Tcam { + pipe: u32, + mau: u32, + tcam_block: regs::TcamsInstance, +} + +impl fmt::Display for Tcam { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[pipe: {}, mau: {}]", self.pipe, self.mau) + } +} + +impl Tcam { + fn handle_lc_err( + &self, + tf: &Tofino, + log: &slog::Logger, + lce: u8, + ) -> IntrResult<()> { + for bit in 0..CHANNEL_PAIRS { + if lce & 1 << bit != 0 { + let lel = self + .tcam_block + .tcam_logical_channel_errlog_lo(bit as u32) + .unwrap() + .read(tf)?; + let leh = self + .tcam_block + .tcam_logical_channel_errlog_hi(bit as u32) + .unwrap() + .read(tf)?; + error!( + log, + "logical channel mismatch on TCAM {} with pair {}: \ + lo channal (addr: 0x{:x} hit: {} action: {}) \ + hi channal (addr: 0x{:x} hit: {} action: {})", + self, + bit, + lel.get_tcam_logical_channel_errlog_addr(), + lel.get_tcam_logical_channel_errlog_hit(), + lel.get_tcam_logical_channel_errlog_actionbit(), + leh.get_tcam_logical_channel_errlog_addr(), + leh.get_tcam_logical_channel_errlog_hit(), + leh.get_tcam_logical_channel_errlog_actionbit() + ); + } + } + + Ok(()) + } + + fn handle_sb_err( + &self, + tf: &Tofino, + log: &slog::Logger, + sbe: u16, + ) -> IntrResult<()> { + for row in 0..TCAM_ROWS { + if sbe & 1 << row != 0 { + let errlog = self + .tcam_block + .tcam_sbe_errlog(row as u32) + .unwrap() + .read(tf)?; + + error!( + log, + "single-bit error on TCAM {}. row: {} addr: 0x{:x}", + self, + row, + u32::from(errlog.get_tcam_sbe_errlog_addr()) + ); + } + } + Ok(()) + } +} + +impl Interrupt for Tcam { + fn process( + &mut self, + tf: &Tofino, + log: &slog::Logger, + status_raw: u32, + ) -> IntrResult { + let status: IntrStatusMauTcamArray = status_raw.into(); + let lce = u8::from(status.get_tcam_logical_channel_err()); + let sbe = u16::from(status.get_tcam_sbe()); + let handled = lce > 0 || sbe > 0; + if lce > 0 { + self.handle_lc_err(tf, log, lce)?; + } + if sbe > 0 { + self.handle_sb_err(tf, log, sbe)?; + } + Ok(handled) + } + + fn set_enable(&mut self, ena_raw: &mut u32, v: bool) { + if v { + let mut ena: IntrEnable0MauTcamArray = (*ena_raw).into(); + ena.set_tcam_logical_channel_err(BitSet::<4>::max()); + ena.set_tcam_sbe(BitSet::<12>::max()); + *ena_raw = ena.into(); + } else { + *ena_raw = 0; + } + } +} + +pub fn groups(tf: &Tofino) -> Vec { + // There is a separate shadow interrupt ID for the TCAM on each pipe and + // MAU. They are assigned sequentially within a pipe, but the starting + // point for each pipe comes from the shadow interrupt table. + let pipe_bases = [256, 320, 384, 448]; + let mut m = Vec::new(); + for pipe in 0..4 { + for mau in 0..20 { + for s in 0..2 { + let tcam_block = tf + .rpi + .pipes(pipe) + .expect("tofino has 4 pipes") + .mau(mau) + .expect("tofino has 20 stages") + .tcams(); + let shadow = pipe_bases[pipe as usize] + mau * 2 + s; + + let group = InterruptGroup::new( + format!("TCAM ECC pipe: {pipe} mau: {mau}"), + vec![shadow], + tcam_block.intr_enable_0_mau_tcam_array(), + tcam_block.intr_status_mau_tcam_array(), + vec![Box::new(Tcam { pipe, mau, tcam_block })], + ); + m.push(group); + } + } + } + m +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 58065534..93f0662b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,4 @@ [toolchain] -# We choose a specific toolchain (rather than "stable") for repeatability. The -# intent is to keep this up-to-date with recently-released stable Rust. -channel = "1.90.0" profile = "default" +# We need to use a nightly toolchain because Bitset uses generic_const_exprs. +channel = "nightly-2026-05-20" diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 87fedc8a..ab2c0bf0 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -140,7 +140,7 @@ pub fn build( println!("using p4 compiler at {p4c_path}"); let tgt_path = format!("{root}/target/proto/opt/oxide/dendrite/{app_name}"); - println!("building p4 payload in: {}", &tgt_path); + println!("building p4 payload in: {}", tgt_path); fs::create_dir_all(Path::new(&tgt_path))?; diff --git a/xtask/src/illumos.rs b/xtask/src/illumos.rs index 3cb7b2d8..4ceb0b51 100644 --- a/xtask/src/illumos.rs +++ b/xtask/src/illumos.rs @@ -103,7 +103,7 @@ fn collect_sde(dst: &str, p4_root: &str) -> Result<()> { fn illumos_package() -> Result<()> { let dist_root = "target/dist"; fs::create_dir_all(dist_root).with_context(|| "Creating {dist_root}")?; - let manifest = format!("{}/manifest", &dist_root); + let manifest = format!("{}/manifest", dist_root); let proto_root = "target/proto"; let fmri = format!("pkg://oxide/system/sidecar@{}", env!("CARGO_PKG_VERSION")); @@ -145,7 +145,7 @@ fn illumos_package() -> Result<()> { } // build a temporary repo - let repo_dir = format!("{}/repo", &dist_root); + let repo_dir = format!("{}/repo", dist_root); fs::create_dir_all(&repo_dir)?; let _ = fs::remove_dir_all(&repo_dir); let status = Command::new("/usr/bin/pkgrepo") @@ -242,7 +242,7 @@ pub async fn dist( format: DistFormat, ) -> Result<()> { let proto_root = "target/proto"; - let opt_root = format!("{}/opt/oxide/dendrite", &proto_root); + let opt_root = format!("{}/opt/oxide/dendrite", proto_root); let bin_root = format!("{opt_root}/bin"); let p4_root = format!("{opt_root}/sidecar");