From be3260d30f4d73fb5f7e15ebf093f1399523c059 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 28 Mar 2025 18:02:40 +0000 Subject: [PATCH 01/12] lisa._assets.kmodules.lisa: Update dependencies --- .../lisa/rust/lisakmod-macros/Cargo.lock | 69 +++++++++++--- .../rust/lisakmod-macros/macros/Cargo.lock | 53 ++++++++--- .../rust/lisakmod-macros/macros/Cargo.toml | 2 +- .../rust/lisakmod-macros/macros/src/misc.rs | 2 +- .../kmodules/lisa/rust/lisakmod/Cargo.lock | 94 ++++++++++++++----- .../kmodules/lisa/rust/lisakmod/Cargo.toml | 3 +- 6 files changed, 168 insertions(+), 55 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock index b99020f2a..76baf9ec2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.lock @@ -2,28 +2,50 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lisakmod_macros" @@ -38,6 +60,7 @@ name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", @@ -51,27 +74,33 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.92" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -80,12 +109,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock index 4745fa07a..3fff3b345 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "cfg-if" version = "1.0.0" @@ -10,20 +16,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] [[package]] name = "libc" -version = "0.2.164" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "lisakmod_macros_proc" @@ -37,27 +44,33 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.89" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -66,12 +79,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml index e20f52062..e5b266e1e 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" syn = { version = "2.0", default-features = false, features = ["proc-macro", "parsing", "printing", "full", "clone-impls"]} quote = { version = "1.0", default-features = false } proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"]} -getrandom = {version = "0.2", default-features = false } +getrandom = {version = "0.3.2", default-features = false } [lib] proc-macro = true diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs index 291ae63f7..0c455acaf 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs @@ -8,7 +8,7 @@ use syn::{Error, Expr, Ident, Token, punctuated::Punctuated}; // time. pub(crate) fn get_random() -> u128 { let mut buf: [u8; 128 / 8] = [0; 128 / 8]; - getrandom::getrandom(&mut buf).expect("Could not get random number"); + getrandom::fill(&mut buf).expect("Could not get random number"); u128::from_le_bytes(buf) } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock index 40e3bc50c..48003f8f8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock @@ -4,9 +4,15 @@ version = 4 [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "cfg-if" @@ -16,50 +22,51 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linkme" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae" +checksum = "22d227772b5999ddc0690e733f734f95ca05387e329c4084fe65678c51198ffe" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9" +checksum = "71a98813fa0073a317ed6a8055dcd4722a49d9b862af828ee68449adb799b6be" dependencies = [ "proc-macro2", "quote", @@ -74,6 +81,7 @@ dependencies = [ "itertools", "linkme", "lisakmod_macros", + "pin-project", ] [[package]] @@ -100,29 +108,55 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "syn" -version = "2.0.92" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -131,12 +165,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml index dac8d0ad6..05240ea55 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml @@ -8,10 +8,11 @@ crate-type = ["staticlib"] [dependencies] anyhow = { version = "1.0", default-features = false } -itertools = {version = "0.13", default-features = false, features = ["use_alloc"]} +itertools = {version = "0.14", default-features = false, features = ["use_alloc"]} linkme = "0.3.31" # hashbrown = "0.15" lisakmod_macros = { path = "../lisakmod-macros" } +pin-project = "1.1" [profile.release] panic = 'abort' -- GitLab From ddd2e7aaa8db81dab8cd4a7c0d217665ced18bcc Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:08:58 +0100 Subject: [PATCH 02/12] lisa._assets.kmodules.lisa: Amend tracepoint binding Use a tuple of argument types rather than a function pointer type to model the tracepoint parameters. --- .../rust/lisakmod/src/runtime/tracepoint.rs | 103 +++++++++--------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs index 5ecf09978..190edb9f2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs @@ -1,17 +1,19 @@ /* SPDX-License-Identifier: GPL-2.0 */ -use alloc::{ffi::CString, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, ffi::CString, vec::Vec}; use core::{ any::Any, cell::UnsafeCell, ffi::{CStr, c_int, c_void}, fmt, marker::PhantomData, + pin::Pin, }; +use lisakmod_macros::inlinec::{cfunc, opaque_type}; + use crate::{ error::{Error, error}, - inlinec::{cfunc, opaque_type}, runtime::{ printk::pr_debug, sync::{Lock as _, LockdepClass, Mutex}, @@ -24,20 +26,20 @@ opaque_type!( "linux/tracepoint.h", ); -pub struct Tracepoint<'tp, F> { +pub struct Tracepoint<'tp, Args> { // struct tracepoint API allows mutation but protects it with an internal mutex // (tracepoint_mutex static variable in tracepoint.c) c_tp: &'tp UnsafeCell, - _phantom: PhantomData, + _phantom: PhantomData, } // SAFETY: The Tracepoint is safe to pass around as it only stores shared references. -unsafe impl<'tp, F> Send for Tracepoint<'tp, F> {} +unsafe impl<'tp, Args> Send for Tracepoint<'tp, Args> {} // SAFETY: the tracepoint kernel API is protected with "tracepoints_mutex" mutex (see // tracepoint.c), so it safe to call it from multiple threads. -unsafe impl<'tp, F> Sync for Tracepoint<'tp, F> {} +unsafe impl<'tp, Args> Sync for Tracepoint<'tp, Args> {} -impl Tracepoint<'static, F> { +impl Tracepoint<'static, Args> { /// # Safety /// /// The tracepoint being looked up must be defined in the base kernel image, not in a module. @@ -96,7 +98,7 @@ pub enum TracepointError { Kernel(c_int), } -impl<'tp, F> Tracepoint<'tp, F> { +impl<'tp, Args> Tracepoint<'tp, Args> { // We cannot use the facilities of opaque_type!() as they require a &Self, which we cannot // safely materialize for CTracepoint as there may be concurrent users of the struct. pub fn name<'a>(&'a self) -> &'a str { @@ -115,8 +117,8 @@ impl<'tp, F> Tracepoint<'tp, F> { pub fn register_probe<'probe>( &'probe self, - probe: &'probe Probe<'probe, F>, - ) -> Result, Error> + probe: &'probe Probe<'probe, Args>, + ) -> Result, Error> where 'tp: 'probe, { @@ -127,34 +129,28 @@ impl<'tp, F> Tracepoint<'tp, F> { #[cfunc] unsafe fn register( tp: *mut CTracepoint, - probe: *mut c_void, - data: *mut c_void, + probe: *const c_void, + data: *const c_void, ) -> Result<(), c_int> { r#" #include "#; r#" - return tracepoint_probe_register(tp, probe, data); + return tracepoint_probe_register(tp, CONST_CAST(void *, probe), CONST_CAST(void *, data)); "# } // SAFETY: If the Probe is alive, then its closure is alive too. Probe will be kept alive // until the last RegisteredProbe is dropped, at which point we known that // tracepoint_probe_unregister() has been called. - unsafe { - register( - self.c_tp.get(), - probe.probe as *mut c_void, - probe.closure as *mut c_void, - ) - } - .map_err(|code| error!("Tracepoint probe registration failed: {code}"))?; + unsafe { register(self.c_tp.get(), probe.probe, probe.closure) } + .map_err(|code| error!("Tracepoint probe registration failed: {code}"))?; Ok(RegisteredProbe { probe, tp: self }) } } -impl<'tp, F> fmt::Debug for Tracepoint<'tp, F> { +impl<'tp, Args> fmt::Debug for Tracepoint<'tp, Args> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.debug_struct("Tracepoint") .field("name", &self.name()) @@ -162,12 +158,12 @@ impl<'tp, F> fmt::Debug for Tracepoint<'tp, F> { } } -pub struct RegisteredProbe<'probe, F> { - probe: &'probe Probe<'probe, F>, - tp: &'probe Tracepoint<'probe, F>, +pub struct RegisteredProbe<'probe, Args> { + probe: &'probe Probe<'probe, Args>, + tp: &'probe Tracepoint<'probe, Args>, } -impl<'probe, F> Drop for RegisteredProbe<'probe, F> { +impl<'probe, Args> Drop for RegisteredProbe<'probe, Args> { fn drop(&mut self) { /// # Safety /// @@ -176,29 +172,23 @@ impl<'probe, F> Drop for RegisteredProbe<'probe, F> { #[cfunc] unsafe fn unregister( tp: *mut CTracepoint, - probe: *mut c_void, - data: *mut c_void, + probe: *const c_void, + data: *const c_void, ) -> Result<(), c_int> { r#" #include "#; r#" - return tracepoint_probe_unregister(tp, probe, data); + return tracepoint_probe_unregister(tp, CONST_CAST(void *, probe), CONST_CAST(void *, data)); "# } let probe = self.probe; // SAFETY: Since we only create RegisteredProbe values when the registration succeeded, we // ensure that this will be the only matching call to tracepoint_probe_unregister() - unsafe { - unregister( - self.tp.c_tp.get(), - probe.probe as *mut c_void, - probe.closure as *mut c_void, - ) - } - .expect("Failed to unregister tracepoint probe"); + unsafe { unregister(self.tp.c_tp.get(), probe.probe, probe.closure as *mut _) } + .expect("Failed to unregister tracepoint probe"); pr_debug!( "Called tracepoint_probe_unregister() for a probe attached to {:?}", @@ -208,7 +198,7 @@ impl<'probe, F> Drop for RegisteredProbe<'probe, F> { } pub struct ProbeDropper { - droppers: Mutex>>>, + droppers: Mutex>>>>, } impl fmt::Debug for ProbeDropper { @@ -263,32 +253,35 @@ impl Drop for ProbeDropper { } } -pub struct Probe<'probe, F> { - closure: *const (), - probe: *mut (), - _phantom: PhantomData<&'probe F>, +pub struct Probe<'probe, Args> { + closure: *const c_void, + probe: *const c_void, + _phantom: PhantomData<(&'probe (), Args)>, } -impl<'probe, F> Probe<'probe, F> { +impl<'probe, Args> Probe<'probe, Args> { /// # Safety /// /// The `probe` must be compatible with the [`Closure`] type in use. #[inline] pub unsafe fn __private_new( - closure: Arc, - probe: *mut (), + closure: Pin>, + probe: *const c_void, + // SAFETY: we borrow ProbeDropper for &'probe, which ensures the ProbeDropper will not be + // dropped before Probe<'probe, _> dropper: &'probe ProbeDropper, - ) -> Probe<'probe, F> + ) -> Probe<'probe, Args> where Closure: 'static + Send + Sync, { - let ptr = Arc::as_ptr(&closure) as *const (); + let ptr: &Closure = &closure; + let ptr: *const Closure = ptr; dropper.droppers.lock().push(Some(closure)); // SAFETY: the probe and closure pointer we store here are guaranteed to be valid for the // lifetime of the Probe object, as we borrow the ProbeDropper for that duration. Probe { - closure: ptr, + closure: ptr as *const c_void, probe, _phantom: PhantomData, } @@ -296,9 +289,9 @@ impl<'probe, F> Probe<'probe, F> { } // SAFETY: this is ok as the closure we store is also Send -unsafe impl<'probe, F> Send for Probe<'probe, F> {} +unsafe impl<'probe, Args> Send for Probe<'probe, Args> {} // SAFETY: this is ok as the closure we store is also Sync -unsafe impl<'probe, F> Sync for Probe<'probe, F> {} +unsafe impl<'probe, Args> Sync for Probe<'probe, Args> {} macro_rules! new_probe { ($dropper:expr, ( $($arg_name:ident: $arg_ty:ty),* ) $body:block) => { @@ -307,13 +300,13 @@ macro_rules! new_probe { // soundly implement Send and Sync type Closure = impl Fn($($arg_ty),*) + ::core::marker::Send + ::core::marker::Sync + 'static; - let closure: ::alloc::sync::Arc = ::alloc::sync::Arc::new( + let closure: ::core::pin::Pin<::alloc::boxed::Box> = ::alloc::boxed::Box::pin( move |$($arg_name: $arg_ty),*| { $body } ); - #[$crate::inlinec::cexport] + #[::lisakmod_macros::inlinec::cexport] fn probe(closure: *const c_void, $($arg_name: $arg_ty),*) { let closure = closure as *const Closure; // SAFETY: Since we call tracepoint_probe_unregister() in ::__private_new( + $crate::runtime::tracepoint::Probe::<( $($arg_ty),* )>::__private_new( closure, - probe as *mut (), + probe as *const c_void, $dropper, ) } } } } + +#[allow(unused_imports)] pub(crate) use new_probe; -- GitLab From 62583276758dc27a570a6743798d74edd277aea8 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:12:38 +0100 Subject: [PATCH 03/12] lisa._assets.kmodules.lisa: Add incomplete_opaque_type!() FEATURE --- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs index 65cd2be39..c1f66942a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -1065,7 +1065,9 @@ make_getaligned!( 262144, 524288 ); -pub trait Opaque { +pub trait Opaque {} + +pub trait SizedOpaque: Opaque { /// # Safety /// /// The passed `init` function must initialize fully the new value, so that calling @@ -1152,7 +1154,7 @@ macro_rules! __internal_opaque_type { // this member. The member in question is an array of u8, which is FFI-safe. #[repr(transparent)] $vis struct $name { - // Since we cannot make Opaque types aligned with a simple attribute + // Since we cannot make opaque types aligned with a simple attribute // (#[repr(align(my_macro!()))] is rejected since my_macro!() is not an integer // literal), we add a zero-sized member that allows specifying the alignment as a // generic const parameter. @@ -1209,8 +1211,10 @@ macro_rules! __internal_opaque_type { $crate::inlinec::__impl_primitive_ptr!($name, $c_name, Some($c_header)); - use $crate::inlinec::Opaque as _; + use $crate::inlinec::{Opaque as _, SizedOpaque as _}; impl $crate::inlinec::Opaque for $name {} + impl $crate::inlinec::SizedOpaque for $name {} + impl $crate::inlinec::FfiType for $name { type FfiType = $name; const C_TYPE: &'static str = $c_name; @@ -1337,6 +1341,30 @@ macro_rules! __internal_opaque_type { // namespace under its private name). pub use crate::__internal_opaque_type as opaque_type; +#[macro_export] +macro_rules! __internal_incomplete_opaque_type { + ($vis:vis struct $name:ident, $c_name:literal, $c_header:expr) => { + #[repr(transparent)] + $vis struct $name { + _data: ::core::ffi::c_void, + } + + $crate::inlinec::__impl_primitive_ptr!($name, $c_name, Some($c_header)); + + use $crate::inlinec::Opaque as _; + impl $crate::inlinec::Opaque for $name {} + + // No FromFfi or IntoFfi implementations as we cannot manipulate values directly. We + // however do provide implementation for reference and pointer types + impl $crate::inlinec::FfiType for $name { + type FfiType = $name; + const C_TYPE: &'static str = $c_name; + const C_HEADER: Option<&'static str> = Some($c_header); + } + }; +} +pub use crate::__internal_incomplete_opaque_type as incomplete_opaque_type; + #[macro_export] macro_rules! __internal_c_static_assert { ($headers:literal, $expr:tt) => {{ -- GitLab From b9bb4ffbcd8e5865b8db15c3c8b2d0d19beb4e65 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:10:17 +0100 Subject: [PATCH 04/12] lisa._assets.kmodules.lisa: Use incomplete_opaque_type!() in traceevent binding --- .../lisa/rust/lisakmod/src/runtime/traceevent.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs index a4d0d8539..72e15175a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs @@ -8,7 +8,7 @@ use core::{ use crate::{ error::{Error, error}, - inlinec::{cfunc, opaque_type}, + inlinec::{cfunc, incomplete_opaque_type, opaque_type}, }; pub trait FieldTy { @@ -60,9 +60,11 @@ opaque_type!( "linux/trace_events.h", ); -// Since "struct synth_event" is an incomplete type, we cannot use opaque_type!() as sizeof(struct -// synth_event) is invalid -type CSynthEvent = core::ffi::c_void; +incomplete_opaque_type!( + pub struct CSynthEvent, + "struct synth_event", + "linux/trace_events.h" +); opaque_type!( struct CSynthFieldDesc, -- GitLab From 365cc052e4825ef5d963612b42bcba5130e3b9b0 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:20:28 +0100 Subject: [PATCH 05/12] lisa._assets.kmodules.lisa: Allow JSON metadata in Rust code FEATURE Allow Rust code to declare some JSON metadata that can later be extracted from the binary in the build system. Also port the exported symbol registration to use this JSON metadata. --- lisa/_assets/kmodules/lisa/Makefile | 33 +++- ...st_exported_symbols.py => process_rust.py} | 101 ++++++---- .../rust/lisakmod-macros/macros/Cargo.lock | 16 ++ .../rust/lisakmod-macros/macros/Cargo.toml | 1 + .../lisakmod-macros/macros/src/inlinec.rs | 48 ++--- .../rust/lisakmod-macros/macros/src/lib.rs | 18 +- .../rust/lisakmod-macros/macros/src/misc.rs | 175 +++++++++++++++++- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 16 ++ .../lisa/rust/lisakmod-macros/src/misc.rs | 13 +- .../rust/lisakmod-macros/src/private/misc.rs | 10 - .../kmodules/lisa/rust/lisakmod/Cargo.lock | 1 + 11 files changed, 334 insertions(+), 98 deletions(-) rename lisa/_assets/kmodules/lisa/{rust_exported_symbols.py => process_rust.py} (52%) diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 033365cc3..d205e94c4 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -86,6 +86,8 @@ RUST_OBJECT_RAW := $(RUST_BUILD_DIR)/rust.raw.o RUST_SYMBOLS := $(RUST_BUILD_DIR)/exported.list RUST_SYMBOLS_CLI := $(RUST_BUILD_DIR)/exported.cli RUST_START_STOP_LDS := $(RUST_BUILD_DIR)/start_stop.lds +RUST_JSON_DATA_DIR := $(RUST_BUILD_DIR)/rust_data/ +RUST_JSON_DATA := $(RUST_BUILD_DIR)/rust_data.json RUST_CBINDGEN_H := $(RUST_SRC)/cbindgen.h RUST_CBINDGEN_BIN := $(CARGO_HOME)/bin/cbindgen @@ -107,7 +109,7 @@ export LISA_EVAL_C_CFLAGS=$(shell echo $(c_flags)) rust_cmd = chmod +x '$(LISA_EVAL_C)' && export RUSTC_BOOTSTRAP=1 PATH="$(CARGO_HOME)/bin:$$PATH" 'RUSTUP_HOME=$(RUSTUP_HOME)' 'CARGO_HOME=$(CARGO_HOME)' 'CARGO_TARGET_DIR=$(CARGO_TARGET_DIR)' 'LISA_EVAL_C=$(LISA_EVAL_C)' && $(1) cargo_cmd = $(call rust_cmd,cargo +$(RUST_VERSION) $(1)) -$(CARGO_TARGET_DIR) $(GENERATED) $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_C_SHIMS_DIR): +$(CARGO_TARGET_DIR) $(GENERATED) $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_C_SHIMS_DIR) $(RUST_JSON_DATA_DIR): mkdir -p "$@" # Build the rust code into a static archive, then prelink it into an object @@ -121,7 +123,20 @@ $(RUST_OBJECT_RAW): $(RUST_BUILD_DIR) $(CARGO_TARGET_DIR) $(LD) $(KBUILD_LDFLAGS) -nostdlib -r -o $(RUST_OBJECT_RAW) --whole-archive $(CARGO_TARGET_DIR)/target/release/liblisakmod.a -$(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) +define extract-binstore + cp $(1) $(1)_objdump.o + $(OBJDUMP) -h $(1)_objdump.o | awk '{ print $$2 }' | grep '^.binstore.$(2)' | sort | xargs -r -P$$(nproc) -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(3)/_$$0 $(1) /dev/null' +endef + + +$(RUST_JSON_DATA): $(RUST_BUILD_DIR) $(RUST_JSON_DATA_DIR) $(RUST_OBJECT_RAW) + + touch $(RUST_JSON_DATA) + $(call extract-binstore,$(RUST_OBJECT_RAW),json,$(RUST_JSON_DATA_DIR)) + LC_ALL=C cat $(RUST_JSON_DATA_DIR)/_.binstore.json > $(RUST_JSON_DATA) + + +$(RUST_OBJECT): $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) $(RUST_JSON_DATA) # Get: # * The list of exported symbols # * A linker script with __start_SECNAME and __stop_SECNAME symbols so @@ -130,7 +145,7 @@ $(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) # executable or DSO (a .ko is a relocatable object file) and # therefore never getting those symbols created by the linker # automatically. - python3 "$(MODULE_SRC)/rust_exported_symbols.py" --rust-object $(RUST_OBJECT_RAW) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) + python3 "$(MODULE_SRC)/process_rust.py" --rust-object $(RUST_OBJECT_RAW) --json $(RUST_JSON_DATA) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) # Garbage collect unused sections in the object file, after we have # extracted the binstore sections (otherwise they get discarded). @@ -150,20 +165,20 @@ $(RUST_OBJECT): $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) touch $(RUST_OBJECT_CMD) -$(RUST_C_SHIMS_H): $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) +$(RUST_C_SHIMS_H): $(RUST_GENERATED) $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) # Extract the C shims created by our inlinec Rust module from the # binary, so they can be compiled and linked later. # Create some empty files so that the globbing pattern later on always # matches something. - touch $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header. $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code. + touch $(RUST_C_SHIMS_DIR)/_.binstore.c.header. $(RUST_C_SHIMS_DIR)/_.binstore.c.code. - cp $(RUST_OBJECT_RAW) $(RUST_OBJECT_RAW)_objdump.o - $(OBJDUMP) -h $(RUST_OBJECT_RAW)_objdump.o | awk '{ print $$2 }' | grep '^.binstore.c.' | sort | xargs -P$$(nproc) -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(RUST_C_SHIMS_DIR)/shim_$$0 $(RUST_OBJECT_RAW) /dev/null' - LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header.* > $(RUST_C_SHIMS_H) - LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code.* > $(RUST_C_SHIMS_C) + $(call extract-binstore,$(RUST_OBJECT_RAW),c.,$(RUST_C_SHIMS_DIR)) + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/_.binstore.c.header.* > $(RUST_C_SHIMS_H) + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/_.binstore.c.code.* > $(RUST_C_SHIMS_C) cat $(RUST_C_SHIMS_H) $(RUST_C_SHIMS_C) | if which clang-format 2>&1 1>/dev/null; then clang-format; else cat; fi +$(RUST_C_SHIMS_H): $(RUST_OBJECT_RAW) $(RUST_C_SHIMS_DIR) # This rule is necessary as Kbuild cannot cope with absolute paths on the # object file list to link in the module. Unfortunately, make is not smart diff --git a/lisa/_assets/kmodules/lisa/rust_exported_symbols.py b/lisa/_assets/kmodules/lisa/process_rust.py similarity index 52% rename from lisa/_assets/kmodules/lisa/rust_exported_symbols.py rename to lisa/_assets/kmodules/lisa/process_rust.py index 76ed68af4..fd81d8e11 100755 --- a/lisa/_assets/kmodules/lisa/rust_exported_symbols.py +++ b/lisa/_assets/kmodules/lisa/process_rust.py @@ -23,70 +23,104 @@ from shlex import quote import subprocess import os import textwrap +import json +from functools import cache +from operator import itemgetter +import textwrap +import itertools +import json + + +SEP = '\n ' + + +def parse_json(path): + content = Path(path).read_text() + print(f'JSON data:\n{content}') + return [ + json.loads(line) + for line in content.splitlines() + ] + def main(): parser = argparse.ArgumentParser(""" - Get the list of exported Rust functions to make it available to C code (and - not garbage collect these entry points it when linking). + Process the Rust object file and associated JSON data. """) - parser.add_argument('--rust-object', help='Built Rust object file', required=True) - parser.add_argument('--out-symbols-plain', help='File to write the symbol list, one per line') - parser.add_argument('--out-symbols-cli', help='File to write the symbol list as ld CLI --undefined options') + parser.add_argument('--rust-object', help='Built Rust object file') + parser.add_argument('--json', help='JSON data extracted from the Rust object file') + parser.add_argument('--out-symbols-plain', help='File to write the exported symbol list, one per line') + parser.add_argument('--out-symbols-cli', help='File to write the exported symbol list as ld CLI --undefined options') parser.add_argument('--out-start-stop-lds', help='File to write the linker script to provide __start_SECNAME and __stop_SECNAME symbols, which are not provided by Kbuild since the module is never linked into an executable or DSO') args = parser.parse_args() - nm = os.environ.get('NM', 'nm') - out = subprocess.check_output( - [nm, '-gj', args.rust_object], - ) - all_symbols = sorted(set(out.decode().split())) - - def parse(sym, prefix): - if sym.startswith(prefix): - sym = sym[len(prefix):] - return sym + @cache + def get_symbols(): + nm = os.environ.get('NM', 'nm') + path = args.rust_object + if path is None: + parser.error(f'--rust-object needs to be specified to get symbols list') else: - return None - - # For each symbol we want to export in Rust, we create a companion symbol - # with a prefix that we pick up here. There unfortunately seems to be no - # cleaner way to convey the list of exported symbols from Rust code as of - # 2024. - user_symbols = [ - sym - for _sym in all_symbols - if (sym := parse(_sym, '__export_rust_symbol_')) - ] + out = subprocess.check_output( + [nm, '-gj', str(path)], + ) - symbols = sorted(user_symbols) + all_symbols = sorted(set(out.decode().split())) + return all_symbols - sep = '\n ' - print(f'Found exported symbols:{sep}{sep.join(symbols)}') + @cache + def get_data(): + path = args.json + if path is None: + parser.error(f'--json needs to be specified for JSON-related features') + else: + return parse_json(path) + + @cache + def get_exported_symbols(): + # We create a JSON entry for each symbol we want to export in Rust. There + # unfortunately seems to be no cleaner way to convey the list of exported + # symbols from Rust code as of 2024. + user_symbols = [ + entry["symbol"] + for entry in get_data() + if entry["type"] == "export-symbol" + ] + symbols = sorted(user_symbols) + print(f'Found exported symbols:{SEP}{SEP.join(symbols)}') + return symbols if (path := args.out_symbols_plain): - content = '\n'.join(symbols) + '\n' + content = '\n'.join(get_exported_symbols()) + '\n' Path(path).write_text(content) if (path := args.out_symbols_cli): content = ' '.join(( f'--undefined {quote(sym)}' - for sym in symbols + for sym in get_exported_symbols() )) + '\n' Path(path).write_text(content) if (path := args.out_start_stop_lds): + def parse(sym, prefix): + if sym.startswith(prefix): + sym = sym[len(prefix):] + return sym + else: + return None + sections = sorted({ section - for sym in all_symbols + for sym in get_symbols() if ( (section := parse(sym, '__start_')) or (section := parse(sym, '__stop_')) ) }) - print(f'Found sections to generate __start_SECNAME and __stop_SECNAME symbols for:{sep}{sep.join(sections)}') + print(f'Found sections to generate __start_SECNAME and __stop_SECNAME symbols for:{SEP}{SEP.join(sections)}') def make_lds(section): return textwrap.dedent(f''' @@ -102,5 +136,6 @@ def main(): lds = '\n'.join(map(make_lds, sections)) + '\n' Path(path).write_text(lds) + if __name__ == '__main__': main() diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock index 3fff3b345..cf1c7160a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.lock @@ -14,6 +14,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "getrandom" version = "0.3.2" @@ -26,6 +32,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "libc" version = "0.2.171" @@ -37,6 +52,7 @@ name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml index e5b266e1e..08e9bbfdc 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml @@ -8,6 +8,7 @@ syn = { version = "2.0", default-features = false, features = ["proc-macro", "pa quote = { version = "1.0", default-features = false } proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"]} getrandom = {version = "0.3.2", default-features = false } +itertools = "0.14.0" [lib] proc-macro = true diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs index f69d304ec..aa162b198 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs @@ -10,7 +10,13 @@ use syn::{ parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token, }; -use crate::misc::{concatcp, get_random}; +use crate::misc::{_dump_to_binstore, concatcp, get_random}; + +fn _export_symbol(sym: Ident) -> Result { + Ok(quote! { + ::lisakmod_macros::misc::export_symbol!(#sym); + }) +} struct CFuncInput { name: Ident, @@ -131,8 +137,8 @@ fn make_c_func( )?; let c_out = [ - make_c_out(c_out, &format!(".binstore.c.code.{}", c_name))?, - make_c_out(c_header_out, &format!(".binstore.c.header.{}", c_name))?, + _dump_to_binstore(&format!("c.code.{}", c_name), c_out)?, + _dump_to_binstore(&format!("c.header.{}", c_name), c_header_out)?, ]; Ok(quote! { @@ -664,38 +670,6 @@ pub fn cexport(attrs: TokenStream, code: TokenStream) -> Result Result { - let ident = syn::parse2::(args)?; - _export_symbol(ident) -} - -fn _export_symbol(ident: Ident) -> Result { - let marker = format_ident!("__export_rust_symbol_{ident}"); - - Ok(quote! { - const _:() = { - #[used] - #[unsafe(no_mangle)] - static #marker: () = (); - }; - }) -} - -fn make_c_out(c_code: TokenStream, section: &str) -> Result { - Ok(quote! { - const _: () = { - const CODE_SLICE: &[u8] = #c_code.as_bytes(); - const CODE_LEN: usize = CODE_SLICE.len(); - - // Store the C function in a section of the binary, that will be extracted by the - // module Makefile and compiled separately as C code. - #[unsafe(link_section = #section)] - #[used] - static CODE: [u8; CODE_LEN] = ::lisakmod_macros::private::misc::slice_to_array::<{CODE_LEN}>(CODE_SLICE); - }; - }) -} - struct CStaticInput { name: Ident, c_code: ( @@ -810,7 +784,7 @@ pub fn cstatic(attrs: TokenStream, code: TokenStream) -> Result Result TokenStream { convert(inlinec::cconstant(args.into())) } -#[proc_macro] -pub fn export_symbol(args: TokenStream) -> TokenStream { - convert(inlinec::export_symbol(args.into())) +#[proc_macro_attribute] +pub fn cstatic(attrs: TokenStream, code: TokenStream) -> TokenStream { + convert(inlinec::cstatic(attrs.into(), code.into())) } #[proc_macro] @@ -39,7 +40,12 @@ pub fn concatcp(args: TokenStream) -> TokenStream { convert(misc::concatcp(args.into())) } -#[proc_macro_attribute] -pub fn cstatic(attrs: TokenStream, code: TokenStream) -> TokenStream { - convert(inlinec::cstatic(attrs.into(), code.into())) +#[proc_macro] +pub fn dump_to_binstore(args: TokenStream) -> TokenStream { + convert(misc::dump_to_binstore(args.into())) +} + +#[proc_macro] +pub fn json_metadata(args: TokenStream) -> TokenStream { + convert(misc::json_metadata(args.into())) } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs index 0c455acaf..afdb8578e 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs @@ -1,8 +1,20 @@ /* SPDX-License-Identifier: Apache-2.0 */ -use proc_macro2::TokenStream; +use itertools::Itertools as _; +use proc_macro2::{Delimiter, TokenStream, TokenTree}; use quote::{format_ident, quote}; -use syn::{Error, Expr, Ident, Token, punctuated::Punctuated}; +use syn::{Error, Expr, ExprLit, Ident, Lit, Token, punctuated::Punctuated, spanned::Spanned}; + +fn try_expand(tokens: TokenStream) -> Result { + let tokens: proc_macro::TokenStream = tokens.into(); + match tokens.expand_expr() { + // As of 03/2025, TokenStream::expand_expr() will not evaluate + // const expressions (e.g. trait associated constants), only + // macros. + Err(_) => Err(tokens.into()), + Ok(tokens) => Ok(tokens.into()), + } +} // Use getrandom instead of e.g. uuid crate as it has far fewer dependencies, so faster build // time. @@ -24,6 +36,52 @@ pub(crate) fn concatcp(args: TokenStream) -> Result { .collect() }; + // Concatenate all string literals we find beforehand to make the generate code more compact. + #[allow(clippy::large_enum_variant)] + enum Item { + LitStr(String), + Other(Expr), + } + impl quote::ToTokens for Item { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Item::LitStr(s) => s.to_tokens(tokens), + Item::Other(expr) => expr.to_tokens(tokens), + } + } + } + + let items: Vec<_> = items + .into_iter() + .map(|item| match try_expand(quote! {#item}) { + Ok(expr) => match syn::parse2::(expr) { + Ok(Expr::Lit(ExprLit { + lit: Lit::Str(litstr), + .. + })) => Item::LitStr(litstr.value()), + _ => Item::Other(item), + }, + Err(_) => Item::Other(item), + }) + .chunk_by(|item| matches!(item, Item::LitStr(_))) + .into_iter() + .flat_map(|(is_lit, chunk)| { + if is_lit { + vec![Item::LitStr( + chunk + .into_iter() + .map(|item| match item { + Item::LitStr(s) => s, + _ => unreachable!(), + }) + .collect(), + )] + } else { + chunk.into_iter().collect() + } + }) + .collect(); + let item_names: Vec = items .iter() .map(|_| format_ident!("__concat_item_{:0>39}", get_random())) @@ -58,3 +116,116 @@ pub(crate) fn concatcp(args: TokenStream) -> Result { // eprintln!("OUT {out}"); Ok(out) } + +pub fn dump_to_binstore(args: TokenStream) -> Result { + let _args = if args.is_empty() { + Vec::new() + } else { + syn::parse::Parser::parse2( + Punctuated::::parse_terminated, + args.clone(), + )? + .into_iter() + .collect() + }; + match &_args[..] { + [subsection, content] => __dump_to_binstore(quote! {#subsection}, quote! {#content}), + _ => Err(Error::new( + args.span(), + "The usage is: dump_to_binstore!(, \"content\") ", + )), + } +} + +pub(crate) fn _dump_to_binstore( + subsection: &str, + content: TokenStream, +) -> Result { + __dump_to_binstore(quote! {#subsection}, content) +} + +pub fn __dump_to_binstore( + subsection: TokenStream, + content: TokenStream, +) -> Result { + Ok(quote! { + const _: () = { + // Allow a TokenStream input so we allow e.g. macro calls etc, rather than just string + // literals. + const CONTENT_STR: &str = #content; + const CONTENT_SLICE: &[u8] = CONTENT_STR.as_bytes(); + const CONTENT_LEN: usize = CONTENT_SLICE.len(); + + // Store the C function in a section of the binary, that will be extracted by the + // module Makefile and the processed (e.g. compiled for C code). + #[unsafe(link_section = ::core::concat!(".binstore.", #subsection))] + #[used] + static CONTENT_ARRAY: [u8; CONTENT_LEN] = { + let mut arr = [0u8; CONTENT_LEN]; + let mut idx: usize = 0; + while idx < CONTENT_LEN { + arr[idx] = CONTENT_SLICE[idx]; + idx += 1; + } + arr + }; + }; + }) +} + +pub fn json_metadata(args: TokenStream) -> Result { + fn process(stream: TokenStream) -> Result { + let tokens: Result, Error> = stream + .into_iter() + .map(|tt| match tt { + TokenTree::Group(ref grp) => { + match grp.delimiter() { + Delimiter::Parenthesis => { + // Allow input to contain parenthesized groups that will be expanded, + // rather than directly stringified to form the JSON output. + let grp = grp.stream(); + match try_expand(grp) { + Err(grp) => { + // Poor man's stringify!(). Ideally, we would evaluate grp to a + // string, and then quote it according to JSON rules. + // Unfortunately, we can't do that easily in a const fn for + // now, so we just add quotes around and hope for the best. + Ok(quote! { "\"", #grp, "\"" }) + } + Ok(grp) => { + // We evaluated to a literal, and now we can stringify!() it so + // that it can be concatenated to the rest. This relies on a + // the Rust native string syntax to be acceptable to a JSON + // parser. Otherwise, we would need to convert the evaluated + // literal to something JSON is happy with, but we cannot do + // that easily. + Ok(quote! {::core::stringify!(#grp)}) + } + } + } + // Braces are allowed as part of the JSON syntax, we just recurse inside + Delimiter::Brace => { + let grp = process(grp.stream())?; + Ok(quote! { "{", #grp, "}" }) + } + Delimiter::Bracket => { + let grp = process(grp.stream())?; + Ok(quote! { "[", #grp, "]" }) + } + Delimiter::None => process(grp.stream()), + } + } + tt => Ok(quote! { ::core::stringify!(#tt) }), + }) + .collect(); + let tokens = tokens?; + Ok(quote! { #(#tokens),* }) + } + let tokens = process(args)?; + let tokens = if tokens.is_empty() { + quote! { "{}" } + } else { + quote! { ::lisakmod_macros::misc::concatcp!(#tokens, "\n") } + }; + _dump_to_binstore("json", tokens) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs index c1f66942a..46df4b1f7 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -1383,3 +1383,19 @@ macro_rules! __internal_c_static_assert { }}; } pub use crate::__internal_c_static_assert as c_static_assert; + +#[macro_export] +macro_rules! __internal_c_eval { + ($header:expr, $expr:literal, $ty:ty) => {{ + // Emit the C function code that will be extracted from the Rust object file and then + // compiled as C. + #[$crate::inlinec::cfunc] + #[allow(non_snake_case)] + fn snippet() -> $ty { + concat!("#include<", $header, ">"); + concat!("return (", $expr, ");") + } + snippet() + }}; +} +pub use crate::__internal_c_eval as c_eval; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs index ac505dbb5..233710f3f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs @@ -1,2 +1,13 @@ /* SPDX-License-Identifier: Apache-2.0 */ -pub use lisakmod_macros_proc::concatcp; +pub use lisakmod_macros_proc::{concatcp, dump_to_binstore, json_metadata}; + +#[macro_export] +macro_rules! __internal_export_symbol { + ($sym:ident) => { + $crate::misc::json_metadata!({ + "type": "export-symbol", + "symbol": (::core::stringify!($sym)) + }); + }; +} +pub use crate::__internal_export_symbol as export_symbol; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs index 5f9e78486..76f56fc2f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs @@ -22,13 +22,3 @@ pub const fn concat(slices: &[&'static str]) -> [u8; N] { } out } - -pub const fn slice_to_array(slice: &[u8]) -> [u8; N] { - let mut arr = [0u8; N]; - let mut idx: usize = 0; - while idx < N { - arr[idx] = slice[idx]; - idx += 1; - } - arr -} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock index 48003f8f8..69a2caee6 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock @@ -97,6 +97,7 @@ name = "lisakmod_macros_proc" version = "0.1.0" dependencies = [ "getrandom", + "itertools", "proc-macro2", "quote", "syn", -- GitLab From e3c875ce80856abfd2213cfbc15531deff92fd8f Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Tue, 25 Mar 2025 19:41:27 +0000 Subject: [PATCH 06/12] lisa._assets.kmodules.lisa: Replace synthetic trace event bindings with TRACE_EVENT() FIX The synthetic trace event API in the upstream kernel is actually unsupported. Therefore, re-implement the trace event binding on top of the traditional TRACE_EVENT() macro. --- lisa/_assets/kmodules/kconfig_fragment.config | 1 - lisa/_assets/kmodules/lisa/Makefile | 3 +- lisa/_assets/kmodules/lisa/ftrace_events.h | 3 + lisa/_assets/kmodules/lisa/process_rust.py | 118 +++++++ .../kmodules/lisa/rust/lisakmod/src/misc.rs | 8 + .../lisa/rust/lisakmod/src/runtime/mod.rs | 6 + .../lisakmod/src/runtime/synth_traceevent.rs | 298 ++++++++++++++++ .../rust/lisakmod/src/runtime/traceevent.rs | 332 ++++-------------- lisa/_assets/kmodules/lisa/utils.h | 19 +- lisa/_kmod.py | 16 +- 10 files changed, 521 insertions(+), 283 deletions(-) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs diff --git a/lisa/_assets/kmodules/kconfig_fragment.config b/lisa/_assets/kmodules/kconfig_fragment.config index 0af706c11..1e5dec710 100644 --- a/lisa/_assets/kmodules/kconfig_fragment.config +++ b/lisa/_assets/kmodules/kconfig_fragment.config @@ -5,4 +5,3 @@ CONFIG_DEBUG_INFO_BTF=y CONFIG_DEBUG_INFO_REDUCED=n CONFIG_BPF_SYSCALL=y CONFIG_FTRACE=y -CONFIG_SYNTH_EVENTS=y diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index d205e94c4..c2490b68b 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -79,6 +79,7 @@ RUST_OBJECT := $(RUST_GENERATED)/rust.o RUST_OBJECT_CMD := $(RUST_GENERATED)/.rust.o.cmd RUST_C_SHIMS_H := $(RUST_GENERATED)/rust_c_shims.h RUST_C_SHIMS_C := $(RUST_GENERATED)/rust_c_shims.c +RUST_TRACE_EVENTS_HEADER := $(RUST_GENERATED)/trace_events.h RUST_BUILD_DIR := $(RUST_GENERATED)/build RUST_C_SHIMS_DIR := $(RUST_BUILD_DIR)/rust_c_shims @@ -145,7 +146,7 @@ $(RUST_OBJECT): $(RUST_GENERATED) $(RUST_BUILD_DIR) $(RUST_OBJECT_RAW) $(RUST_JS # executable or DSO (a .ko is a relocatable object file) and # therefore never getting those symbols created by the linker # automatically. - python3 "$(MODULE_SRC)/process_rust.py" --rust-object $(RUST_OBJECT_RAW) --json $(RUST_JSON_DATA) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) + python3 "$(MODULE_SRC)/process_rust.py" --rust-object $(RUST_OBJECT_RAW) --json $(RUST_JSON_DATA) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) --out-start-stop-lds $(RUST_START_STOP_LDS) --out-trace-events-header $(RUST_TRACE_EVENTS_HEADER) # Garbage collect unused sections in the object file, after we have # extracted the binstore sections (otherwise they get discarded). diff --git a/lisa/_assets/kmodules/lisa/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index af9912153..198d5ba83 100644 --- a/lisa/_assets/kmodules/lisa/ftrace_events.h +++ b/lisa/_assets/kmodules/lisa/ftrace_events.h @@ -15,8 +15,11 @@ #include #include +#include "utils.h" #include "sched_helpers.h" +#include "generated/rust/trace_events.h" + #if HAS_MEMBER(struct, sched_avg, runnable_load_avg) #define RBL_LOAD_ENTRY rbl_load #define RBL_LOAD_MEMBER runnable_load_avg diff --git a/lisa/_assets/kmodules/lisa/process_rust.py b/lisa/_assets/kmodules/lisa/process_rust.py index fd81d8e11..3856a8302 100755 --- a/lisa/_assets/kmodules/lisa/process_rust.py +++ b/lisa/_assets/kmodules/lisa/process_rust.py @@ -54,6 +54,7 @@ def main(): parser.add_argument('--out-symbols-plain', help='File to write the exported symbol list, one per line') parser.add_argument('--out-symbols-cli', help='File to write the exported symbol list as ld CLI --undefined options') parser.add_argument('--out-start-stop-lds', help='File to write the linker script to provide __start_SECNAME and __stop_SECNAME symbols, which are not provided by Kbuild since the module is never linked into an executable or DSO') + parser.add_argument('--out-trace-events-header', help='File to write the ftrace event definitions') args = parser.parse_args() @@ -136,6 +137,123 @@ def main(): lds = '\n'.join(map(make_lds, sections)) + '\n' Path(path).write_text(lds) + if (path := args.out_trace_events_header): + + class Field: + def __init__(self, name, logical_type, c_arg_type): + self.name = name + self.logical_type = logical_type + self.c_arg_type = c_arg_type + + @property + def tp_struct_entry(self): + typ = self.logical_type + # TODO: support arrays + if typ == 'string': + return f'__string({self.name}, {self.name})' + elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): + return f'__field({self.c_arg_type}, {self.name})' + else: + raise ValueError(f'Unsupported logical type: {typ}') + + @property + def entry(self): + return f'__entry->{self.name}' + + @property + def tp_fast_assign(self): + typ = self.logical_type + # TODO: support arrays + if typ == 'string': + return f'__lisa_assign_str({self.name}, {self.name});' + elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): + return f'{self.entry} = {self.name};' + else: + raise ValueError(f'Unsupported logical type: {typ}') + + @property + def tp_printk(self): + typ = self.logical_type + # TODO: support arrays + if typ in ('s8', 's16', 's32'): + return (f'{self.name}=%d', [self.entry]) + elif typ in ('u8', 'u16', 'u32'): + return (f'{self.name}=%u', [self.entry]) + elif typ == 's64': + return (f'{self.name}=%lld', [self.entry]) + elif typ == 'u64': + return (f'{self.name}=%llu', [self.entry]) + elif typ == 'string': + return (f'{self.name}=%s', [f'__get_str({self.name})']) + else: + raise ValueError(f'Unsupported logical type: {typ}') + + def make_event(entry): + name = entry['name'] + fields = [ + Field( + name=field['name'], + logical_type=field['logical-type'], + c_arg_type=field['c-arg-type'], + ) + for field in entry['fields'] + ] + + nl = '\n ' + proto = ', '.join( + f'__typeof__({field.c_arg_type}) {field.name}' + for field in fields + ) + args = ', '.join( + field.name + for field in fields + ) + struct_entry = nl.join( + field.tp_struct_entry + for field in fields + ) + + assign = nl.join( + field.tp_fast_assign + for field in fields + ) + printk_fmts, printk_args = zip(*( + field.tp_printk + for field in fields + )) + printk_fmt = ' '.join(printk_fmts) + # Use json escaping as an easy way to produce a C string literal. + printk_fmt = json.dumps(printk_fmt) + printk_args = ', '.join(itertools.chain.from_iterable(printk_args)) + + return textwrap.dedent(f''' + TRACE_EVENT({name}, + TP_PROTO({proto}), + TP_ARGS({args}), + TP_STRUCT__entry( + {struct_entry} + ), + TP_fast_assign( + {assign} + ), + TP_printk( + {printk_fmt}, {printk_args} + ) + ) + ''') + + events = sorted( + ( + entry + for entry in get_data() + if entry["type"] == "define-ftrace-event" + ), + key=itemgetter('name'), + ) + out = '\n\n'.join(map(make_event, events)) + Path(path).write_text(out) + + if __name__ == '__main__': main() diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs index 8921d0b69..421106734 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs @@ -48,3 +48,11 @@ macro_rules! destructure { }} } pub(crate) use destructure; + +// Join the parameters using the given separator, avoiding any trailing separator +macro_rules! join{ + ($sep:expr, $first:expr $(, $rest:expr)* $(,)?) => { + ::core::concat!($first $(, $sep, $rest)*) + }; +} +pub(crate) use join; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs index 28b0b7058..4ce864bf0 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -9,3 +9,9 @@ pub mod sync; pub mod sysfs; pub mod traceevent; pub mod tracepoint; +pub mod wq; + +// FIXME: remove ? +// This module is not currently used, as synthetic trace events are not actually supported +// upstream. +// pub mod synth_traceevent; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs new file mode 100644 index 000000000..47503d4e8 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +//! This module implements ftrace events bindings using the CONFIG_SYNTH_EVENTS interface, rather +//! than the traditional TRACE_EVENT() macro. + +use alloc::{ffi::CString, string::String, vec::Vec}; +use core::{ + cell::UnsafeCell, + ffi::{CStr, c_int, c_uchar}, +}; + +use crate::{ + error::{Error, error}, + inlinec::{cfunc, incomplete_opaque_type, opaque_type}, +}; + +pub trait FieldTy { + const NAME: &'static str; + + fn field_desc(name: String) -> FieldDesc { + FieldDesc { + c_ty: Self::NAME.into(), + name, + } + } + + fn to_u64(self) -> u64; +} + +macro_rules! impl_field { + ($ty:ty, $c_name:literal, $to_u64:expr) => { + impl FieldTy for $ty { + const NAME: &'static str = $c_name; + + #[inline] + fn to_u64(self) -> u64 { + $to_u64(self) + } + } + }; +} + +impl_field!(u8, "u8", |x| x as u64); +impl_field!(i8, "s8", |x| x as u64); +impl_field!(u16, "u16", |x| x as u64); +impl_field!(i16, "s16", |x| x as u64); +impl_field!(u32, "u32", |x| x as u64); +impl_field!(i32, "s32", |x| x as u64); +impl_field!(u64, "u64", |x| x); +impl_field!(i64, "s64", |x| x as u64); +impl_field!(&CStr, "char[]", |x: &CStr| x.as_ptr() as usize as u64); + +pub struct FieldDesc { + name: String, + c_ty: String, +} + +struct Fields {} + +opaque_type!( + pub struct CEventFile, + "struct trace_event_file", + "linux/trace_events.h", +); + +incomplete_opaque_type!( + pub struct CSynthEvent, + "struct synth_event", + "linux/trace_events.h" +); + +opaque_type!( + struct CSynthFieldDesc, + "struct synth_field_desc", + "linux/trace_events.h", + + attr_accessors {name: *const c_uchar}, + attr_accessors {type as typ: *const c_uchar}, +); + +pub struct EventDesc { + // SAFETY: We need to hold onto those as they have been passed to the synthetic event kernel + // API. + name: CString, + c_field_descs: Vec<(CString, CString)>, + pub __c_event_file: *const UnsafeCell, + pub __c_synth_event: *const UnsafeCell, +} +unsafe impl Send for EventDesc {} + +impl EventDesc { + pub fn new(name: &str, field_descs: Vec) -> Result { + #[cfunc] + unsafe fn make_event<'a, 'b>( + name: &'a CStr, + fields: *mut CSynthFieldDesc, + len: usize, + ) -> Result<(), c_int> { + r#" + #include + #include + "#; + + r#" + return synth_event_create(name, fields, len, THIS_MODULE); + "# + } + + fn to_c_string(s: &str) -> CString { + CString::new(s) + .expect("Cannot convert Rust string to C string if it contains nul bytes") + } + + let c_field_descs: Vec<_> = field_descs + .iter() + .map(|desc| (to_c_string(&desc.name), to_c_string(&desc.c_ty))) + .collect(); + + let name = to_c_string(name); + { + let mut array: Vec = c_field_descs + .iter() + .map(|(name, ty)| { + unsafe { + CSynthFieldDesc::new_stack(move |this| { + this.name_raw_mut().write(name.as_ptr() as *const c_uchar); + this.typ_raw_mut().write(ty.as_ptr() as *const c_uchar); + Ok::<(), core::convert::Infallible>(()) + }) + } + .unwrap() + }) + .collect(); + + // SAFETY: make_event() will fail if an event already exists with that name, so once we + // have successfully created the event, we can rely on any by-name lookup in various + // kernel APIs to always give back data related to what we allocated here. + + // SAFETY: we will hold onto all the CString we created here until we call + // delete_event(), at which point the kernel will not need them anymore. + unsafe { make_event(name.as_c_str(), array.as_mut_ptr(), array.len()) }.map_err( + |code| error!("Synthetic ftrace event {name:?} registration failed: {code}"), + )? + } + + #[cfunc] + unsafe fn synth_event_find(name: &CStr) -> *const UnsafeCell { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_SYMBOL(synth_event_find) + return synth_event_find(name); + #else + return NULL; + #endif + "# + } + + let __c_synth_event = unsafe { synth_event_find(name.as_c_str()) }; + + #[cfunc] + unsafe fn trace_get_event_file<'a, 'b>( + name: &'a CStr, + ) -> Option<&'b UnsafeCell> { + r#" + #include + "#; + + r#" + return trace_get_event_file(NULL, "synthetic", name); + "# + } + let __c_event_file = unsafe { trace_get_event_file(name.as_c_str()) } + .ok_or_else(|| error!("Could not get trace_event_file for synthetic event {name:?}"))?; + + Ok(EventDesc { + name, + c_field_descs, + __c_event_file, + __c_synth_event, + }) + } + + /// # Safety + /// + /// The passed `vals` must be consistent with the event format + #[inline] + pub unsafe fn __trace(&self, vals: &mut [u64]) { + #[cfunc] + unsafe fn trace_synth( + event_file: *const UnsafeCell, + synth_event: *const UnsafeCell, + vals: *mut u64, + n_vals: usize, + ) -> Result<(), c_int> { + r#" + #include + #include "introspection.h" + "#; + + r#" + // As of 6.13, there is no way to emit the event in all instances where it was enabled, only in + // the top-level buffer. This may get solved if the patches mentioned there are merged: + // https://bugzilla.kernel.org/show_bug.cgi?id=219876 + #if HAS_SYMBOL(synth_event_trace2) + static unsigned int var_ref_idx[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + }; + BUG_ON(!synth_event); + BUG_ON(n_vals > ARRAY_SIZE(var_ref_idx)); + synth_event_trace2(synth_event, vals, var_ref_idx); + return 0; + #elif HAS_SYMBOL(synth_event_trace_array) + BUG_ON(!event_file); + return synth_event_trace_array(event_file, vals, n_vals); + #elif !defined(CONFIG_SYNTH_EVENTS) + # error "CONFIG_SYNTH_EVENTS=y is necessary" + #endif + "# + } + unsafe { + trace_synth( + self.__c_event_file, + self.__c_synth_event, + vals.as_mut_ptr(), + vals.len(), + ) + } + .expect("Could not emit synthetic ftrace event"); + } +} + +impl Drop for EventDesc { + fn drop(&mut self) { + #[cfunc] + unsafe fn trace_put_event_file<'a>(event_file: *const UnsafeCell) { + r#" + #include + #include "utils.h" + "#; + + r#" + return trace_put_event_file(CONST_CAST(struct trace_event_file *, event_file)); + "# + } + + #[cfunc] + unsafe fn delete_event(name: &CStr) -> Result<(), c_int> { + r#" + #include + "#; + + r#" + return synth_event_delete(name); + "# + } + + // SAFETY: CEventFile is valid until the event gets deleted, which hasn't happened yet. + unsafe { trace_put_event_file(self.__c_event_file) } + + // This may fail if the kernel in use exhibits this problem: + // https://bugzilla.kernel.org/show_bug.cgi?id=219875 + unsafe { delete_event(self.name.as_c_str()) }.expect("Could not delete synthetic event"); + } +} + +macro_rules! new_event { + ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => { + { + $crate::runtime::traceevent::EventDesc::new( + stringify!($name), + ::alloc::vec![ + $( + <$field_ty as $crate::runtime::traceevent::FieldTy>::field_desc(stringify!($field_name).into()) + ),* + ], + ).map(|event_desc| move |$($field_name: $field_ty),*| { + let mut vals = [ + $( + <$field_ty as $crate::runtime::traceevent::FieldTy>::to_u64($field_name) + ),* + ]; + // SAFETY: The event fields type and order is guaranteed to be consistent between + // the defintion and the use of the array, as they are both created here in the + // new_event!() macro in the same order. + unsafe { + event_desc.__trace(&mut vals) + } + }) + } + }; +} +pub(crate) use new_event; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs index 72e15175a..ead73e580 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs @@ -1,295 +1,81 @@ /* SPDX-License-Identifier: GPL-2.0 */ -use alloc::{ffi::CString, string::String, vec::Vec}; -use core::{ - cell::UnsafeCell, - ffi::{CStr, c_int, c_uchar}, -}; +use core::ffi::CStr; -use crate::{ - error::{Error, error}, - inlinec::{cfunc, incomplete_opaque_type, opaque_type}, -}; +use lisakmod_macros::inlinec::IntoFfi; -pub trait FieldTy { +pub trait FieldTy: IntoFfi { const NAME: &'static str; - - fn field_desc(name: String) -> FieldDesc { - FieldDesc { - c_ty: Self::NAME.into(), - name, - } - } - - fn to_u64(self) -> u64; } macro_rules! impl_field { - ($ty:ty, $c_name:literal, $to_u64:expr) => { + ($ty:ty, $c_name:literal) => { impl FieldTy for $ty { const NAME: &'static str = $c_name; - - #[inline] - fn to_u64(self) -> u64 { - $to_u64(self) - } } }; } -impl_field!(u8, "u8", |x| x as u64); -impl_field!(i8, "s8", |x| x as u64); -impl_field!(u16, "u16", |x| x as u64); -impl_field!(i16, "s16", |x| x as u64); -impl_field!(u32, "u32", |x| x as u64); -impl_field!(i32, "s32", |x| x as u64); -impl_field!(u64, "u64", |x| x); -impl_field!(i64, "s64", |x| x as u64); -impl_field!(&CStr, "char[]", |x: &CStr| x.as_ptr() as usize as u64); - -pub struct FieldDesc { - name: String, - c_ty: String, -} - -struct Fields {} - -opaque_type!( - pub struct CEventFile, - "struct trace_event_file", - "linux/trace_events.h", -); - -incomplete_opaque_type!( - pub struct CSynthEvent, - "struct synth_event", - "linux/trace_events.h" -); - -opaque_type!( - struct CSynthFieldDesc, - "struct synth_field_desc", - "linux/trace_events.h", - - attr_accessors {name: *const c_uchar}, - attr_accessors {type as typ: *const c_uchar}, -); - -pub struct EventDesc { - // SAFETY: We need to hold onto those as they have been passed to the synthetic event kernel - // API. - name: CString, - c_field_descs: Vec<(CString, CString)>, - pub __c_event_file: *const UnsafeCell, - pub __c_synth_event: *const UnsafeCell, -} -unsafe impl Send for EventDesc {} +// New implementations of FieldTy should get a matching implementations in the process_rust.py +// script. +impl_field!(u8, "u8"); +impl_field!(i8, "s8"); +impl_field!(u16, "u16"); +impl_field!(i16, "s16"); +impl_field!(u32, "u32"); +impl_field!(i32, "s32"); +impl_field!(u64, "u64"); +impl_field!(i64, "s64"); +impl_field!(&CStr, "string"); -impl EventDesc { - pub fn new(name: &str, field_descs: Vec) -> Result { - #[cfunc] - unsafe fn make_event<'a, 'b>( - name: &'a CStr, - fields: *mut CSynthFieldDesc, - len: usize, - ) -> Result<(), c_int> { - r#" - #include - #include - "#; - - r#" - return synth_event_create(name, fields, len, THIS_MODULE); - "# - } - - fn to_c_string(s: &str) -> CString { - CString::new(s) - .expect("Cannot convert Rust string to C string if it contains nul bytes") - } - - let c_field_descs: Vec<_> = field_descs - .iter() - .map(|desc| (to_c_string(&desc.name), to_c_string(&desc.c_ty))) - .collect(); - - let name = to_c_string(name); - { - let mut array: Vec = c_field_descs - .iter() - .map(|(name, ty)| { - unsafe { - CSynthFieldDesc::new_stack(move |this| { - this.name_raw_mut().write(name.as_ptr() as *const c_uchar); - this.typ_raw_mut().write(ty.as_ptr() as *const c_uchar); - Ok::<(), core::convert::Infallible>(()) - }) +macro_rules! new_event { + ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => {{ + ::lisakmod_macros::misc::json_metadata!({ + "type": "define-ftrace-event", + "name": (::core::stringify!($name)), + "fields": [ + $( + { + "name": (::core::stringify!($field_name)), + "logical-type": (<$field_ty as $crate::runtime::traceevent::FieldTy>::NAME), + "c-arg-type": (<$field_ty as ::lisakmod_macros::inlinec::FfiType>::C_TYPE) } - .unwrap() - }) - .collect(); - - // SAFETY: make_event() will fail if an event already exists with that name, so once we - // have successfully created the event, we can rely on any by-name lookup in various - // kernel APIs to always give back data related to what we allocated here. - - // SAFETY: we will hold onto all the CString we created here until we call - // delete_event(), at which point the kernel will not need them anymore. - unsafe { make_event(name.as_c_str(), array.as_mut_ptr(), array.len()) }.map_err( - |code| error!("Synthetic ftrace event {name:?} registration failed: {code}"), - )? - } - - #[cfunc] - unsafe fn synth_event_find(name: &CStr) -> *const UnsafeCell { - r#" - #include - #include "introspection.h" - "#; + ),* + ] + }); - r#" - #if HAS_SYMBOL(synth_event_find) - return synth_event_find(name); - #else - return NULL; - #endif - "# - } - - let __c_synth_event = unsafe { synth_event_find(name.as_c_str()) }; - - #[cfunc] - unsafe fn trace_get_event_file<'a, 'b>( - name: &'a CStr, - ) -> Option<&'b UnsafeCell> { - r#" - #include - "#; - - r#" - return trace_get_event_file(NULL, "synthetic", name); - "# - } - let __c_event_file = unsafe { trace_get_event_file(name.as_c_str()) } - .ok_or_else(|| error!("Could not get trace_event_file for synthetic event {name:?}"))?; - - Ok(EventDesc { - name, - c_field_descs, - __c_event_file, - __c_synth_event, - }) - } - - /// # Safety - /// - /// The passed `vals` must be consistent with the event format - #[inline] - pub unsafe fn __trace(&self, vals: &mut [u64]) { - #[cfunc] - unsafe fn trace_synth( - event_file: *const UnsafeCell, - synth_event: *const UnsafeCell, - vals: *mut u64, - n_vals: usize, - ) -> Result<(), c_int> { - r#" - #include - #include "introspection.h" - "#; - - r#" - // As of 6.13, there is no way to emit the event in all instances where it was enabled, only in - // the top-level buffer. This may get solved if the patches mentioned there are merged: - // https://bugzilla.kernel.org/show_bug.cgi?id=219876 - #if HAS_SYMBOL(synth_event_trace2) - static unsigned int var_ref_idx[] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - }; - BUG_ON(!synth_event); - BUG_ON(n_vals > ARRAY_SIZE(var_ref_idx)); - synth_event_trace2(synth_event, vals, var_ref_idx); - return 0; - #elif HAS_SYMBOL(synth_event_trace_array) - BUG_ON(!event_file); - return synth_event_trace_array(event_file, vals, n_vals); - #elif !defined(CONFIG_SYNTH_EVENTS) - # error "CONFIG_SYNTH_EVENTS=y is necessary" - #endif - "# - } - unsafe { - trace_synth( - self.__c_event_file, - self.__c_synth_event, - vals.as_mut_ptr(), - vals.len(), - ) - } - .expect("Could not emit synthetic ftrace event"); - } -} - -impl Drop for EventDesc { - fn drop(&mut self) { - #[cfunc] - unsafe fn trace_put_event_file<'a>(event_file: *const UnsafeCell) { - r#" - #include - #include "utils.h" - "#; - - r#" - return trace_put_event_file(CONST_CAST(struct trace_event_file *, event_file)); - "# - } - - #[cfunc] - unsafe fn delete_event(name: &CStr) -> Result<(), c_int> { - r#" - #include - "#; - - r#" - return synth_event_delete(name); - "# - } - - // SAFETY: CEventFile is valid until the event gets deleted, which hasn't happened yet. - unsafe { trace_put_event_file(self.__c_event_file) } - - // This may fail if the kernel in use exhibits this problem: - // https://bugzilla.kernel.org/show_bug.cgi?id=219875 - unsafe { delete_event(self.name.as_c_str()) }.expect("Could not delete synthetic event"); - } -} - -macro_rules! new_event { - ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => { { - $crate::runtime::traceevent::EventDesc::new( - stringify!($name), - ::alloc::vec![ - $( - <$field_ty as $crate::runtime::traceevent::FieldTy>::field_desc(stringify!($field_name).into()) - ),* - ], - ).map(|event_desc| move |$($field_name: $field_ty),*| { - let mut vals = [ - $( - <$field_ty as $crate::runtime::traceevent::FieldTy>::to_u64($field_name) - ),* - ]; - // SAFETY: The event fields type and order is guaranteed to be consistent between - // the defintion and the use of the array, as they are both created here in the - // new_event!() macro in the same order. - unsafe { - event_desc.__trace(&mut vals) + #[::lisakmod_macros::inlinec::cfunc] + fn emit($($field_name: $field_ty),*) { + r#" + #include "ftrace_events.h" + "#; + + // Call the trace_foo() function created by the TRACE_EVENT(foo, ...) kernel macro + ::core::concat!( + "trace_", ::core::stringify!($name), "(", + $crate::misc::join!( + ", ", + $(::core::stringify!($field_name)),* + ), + ");" + ); + } + + // Wrap the function in a closure so that we can later on switch to a dynamic event + // creation facility if it becomes available. This is achieved by: + // 1. Returning a closure rather than the fn item, so client code is used to dealing + // with a closure. + // 2. Make the closure !Copy, so that client code does not accidentally relies on that. + struct NonCopy; + let x = NonCopy; + Ok::<_, $crate::error::Error>( + move |$($field_name: $field_ty),*| { + let _ = &x; + emit($($field_name),*) } - }) + ) } - }; + }}; } pub(crate) use new_event; diff --git a/lisa/_assets/kmodules/lisa/utils.h b/lisa/_assets/kmodules/lisa/utils.h index 4a55d3e41..acd5c9720 100644 --- a/lisa/_assets/kmodules/lisa/utils.h +++ b/lisa/_assets/kmodules/lisa/utils.h @@ -10,10 +10,12 @@ # include "linux/args.h" #endif -#include "linux/kernel.h" -#include "linux/module.h" -#include "linux/version.h" -#include "linux/stringify.h" +#include +#include +#include +#include +#include + #define IGNORE_WARNING(warning, expr) ({ \ __diag_push(); \ @@ -47,4 +49,13 @@ # define LISA_MODULE_IMPORT_NS(ns) MODULE_IMPORT_NS(__stringify(ns)) #endif +// __assign_str() takes only one parameter since commit 2c92ca849fcc as +// __string() contains the source already. This allows ftrace to reserve the +// appropriate buffer size before-hand. +#if LINUX_VERSION_CODE < KERNEL_VERSION(6,10,0) +# define __lisa_assign_str(field, src) __assign_str(field, src) +#else +# define __lisa_assign_str(field, src) __assign_str(field) +#endif + #endif /* _UTILS_H */ diff --git a/lisa/_kmod.py b/lisa/_kmod.py index d20da9909..b8b2b1a39 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -3307,12 +3307,20 @@ class FtraceDynamicKmod(DynamicKmod): """ decomment = re.compile(rb'^.*//.*?$|/\*.*?\*/', flags=re.MULTILINE) - # We match trace_lisa__XXX tokens, as these are the events that are - # actually used inside the module. - find_event = re.compile(rb'\btrace_(lisa__.*?)\b') + find_event = [ + # We match trace_lisa__XXX tokens, as these are the events that + # are actually used inside the module C code. + re.compile(rb'\btrace_(lisa__.*?)\b'), + # Match the events defined in Rust code + re.compile(rb'\bnew_event\s*!\s*\(\s*(.*?)\s*,', flags=re.DOTALL), + re.compile(rb'\bnew_event\s*!\s*{\s*(.*?)\s*,', flags=re.DOTALL), + ] def find_events(code): code = decomment.sub(b'', code) - return map(bytes.decode, find_event.findall(code)) + return itertools.chain.from_iterable( + map(bytes.decode, regex.findall(code)) + for regex in find_event + ) return sorted({ possible_event -- GitLab From 275797a0a7057bd2f5fa1374a3802c73c06fc576 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:30:16 +0100 Subject: [PATCH 07/12] lisa._assets.kmodules.lisa: Remove lisakmod::inlinec module Remove that module and instead just use items from lisakmod_macros directly. --- .../lisa/rust/lisakmod/src/features/legacy.rs | 5 +++-- .../lisa/rust/lisakmod/src/features/tests.rs | 3 ++- .../kmodules/lisa/rust/lisakmod/src/inlinec.rs | 18 ------------------ .../kmodules/lisa/rust/lisakmod/src/lib.rs | 1 - .../kmodules/lisa/rust/lisakmod/src/prelude.rs | 4 ++-- .../lisa/rust/lisakmod/src/runtime/alloc.rs | 7 +++---- .../lisa/rust/lisakmod/src/runtime/panic.rs | 3 ++- .../lisa/rust/lisakmod/src/runtime/printk.rs | 3 ++- .../lisa/rust/lisakmod/src/runtime/sync.rs | 9 ++++----- .../lisakmod/src/runtime/synth_traceevent.rs | 3 ++- .../lisa/rust/lisakmod/src/runtime/sysfs.rs | 7 +++---- 11 files changed, 23 insertions(+), 40 deletions(-) delete mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs index 8194e027f..3e4e1f4c5 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs @@ -3,9 +3,10 @@ use alloc::{sync::Arc, vec::Vec}; use core::ffi::c_int; +use lisakmod_macros::inlinec::cfunc; + use crate::{ - error::error, features::define_feature, inlinec::cfunc, lifecycle::new_lifecycle, - runtime::printk::pr_err, + error::error, features::define_feature, lifecycle::new_lifecycle, runtime::printk::pr_err, }; define_feature! { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs index 0b5ff6af3..3d7f3267c 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs @@ -3,10 +3,11 @@ use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; use core::ffi::CStr; +use lisakmod_macros::inlinec::{c_eval, cconstant, cfunc}; + use crate::{ error::Error, features::define_feature, - inlinec::{c_eval, cconstant, cfunc}, lifecycle::new_lifecycle, runtime::{ kbox::KernelKBox, diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs deleted file mode 100644 index 6c68998fd..000000000 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -pub use lisakmod_macros::inlinec::*; - -macro_rules! c_eval { - ($header:expr, $expr:literal, $ty:ty) => {{ - // Emit the C function code that will be extracted from the Rust object file and then - // compiled as C. - #[::lisakmod_macros::inlinec::cfunc] - #[allow(non_snake_case)] - fn snippet() -> $ty { - concat!("#include<", $header, ">"); - concat!("return (", $expr, ");") - } - snippet() - }}; -} -pub(crate) use c_eval; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index b2000b35f..e168175c0 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -17,7 +17,6 @@ pub mod features; pub mod fmt; pub mod graph; pub mod init; -pub mod inlinec; pub mod lifecycle; pub mod misc; pub mod prelude; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs index 35c56c56d..f5c379369 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs @@ -2,11 +2,11 @@ #[allow(unused_imports)] pub(crate) use ::alloc::boxed::Box; +#[allow(unused_imports)] +pub(crate) use lisakmod_macros::inlinec::{c_eval, cconstant, cexport, cfunc, cstatic}; #[allow(unused_imports)] pub(crate) use crate::{ - inlinec::c_eval, - inlinec::{cconstant, cexport, cfunc, cstatic}, runtime::kbox::{KBox, KernelKBox}, runtime::printk::{pr_err, pr_info}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs index ee993ac10..b4833aaf7 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -6,10 +6,9 @@ use core::{ ffi::c_void, }; -use crate::{ - inlinec::{cconstant, cfunc}, - runtime::printk::pr_err, -}; +use lisakmod_macros::inlinec::{cconstant, cfunc}; + +use crate::runtime::printk::pr_err; #[inline] fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs index 2ddae08a7..23880cd0f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs @@ -2,9 +2,10 @@ use core::fmt::Write; +use lisakmod_macros::inlinec::cfunc; + use crate::{ fmt::{KBoxWriter, PRINT_PREFIX}, - inlinec::cfunc, runtime::alloc::{GFPFlags, KmallocAllocator}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs index c7c8800bd..ebd457f0a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs @@ -2,9 +2,10 @@ use core::fmt::Write as _; +use lisakmod_macros::inlinec::cfunc; + use crate::{ fmt::{KBoxWriter, PRINT_PREFIX}, - inlinec::cfunc, runtime::alloc::{GFPFlags, KmallocAllocator}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs index 8ae5edd10..61d861b45 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -9,10 +9,9 @@ use core::{ pin::Pin, }; -use crate::{ - inlinec::{cfunc, opaque_type}, - runtime::kbox::KernelKBox, -}; +use lisakmod_macros::inlinec::{cfunc, opaque_type}; + +use crate::runtime::kbox::KernelKBox; opaque_type!(struct CLockClassKey, "struct lock_class_key", "linux/lockdep.h"); @@ -286,7 +285,7 @@ macro_rules! new_static_mutex { ($vis:vis $name:ident, $ty:ty, $data:expr) => { $vis static $name: $crate::runtime::sync::Mutex<$ty> = $crate::runtime::sync::Mutex::__internal_from_ref($data, { - #[$crate::inlinec::cstatic] + #[::lisakmod_macros::inlinec::cstatic] static STATIC_MUTEX: $crate::runtime::sync::CMutex = ( "#include ", "DEFINE_MUTEX(STATIC_VARIABLE);" diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs index 47503d4e8..c1480b6ff 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/synth_traceevent.rs @@ -9,9 +9,10 @@ use core::{ ffi::{CStr, c_int, c_uchar}, }; +use lisakmod_macros::inlinec::{cfunc, incomplete_opaque_type, opaque_type}; + use crate::{ error::{Error, error}, - inlinec::{cfunc, incomplete_opaque_type, opaque_type}, }; pub trait FieldTy { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs index 7726673e6..b73679341 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs @@ -10,10 +10,9 @@ use core::{ pin::Pin, }; -use crate::{ - inlinec::{c_eval, cexport, cfunc, opaque_type}, - misc::{container_of, mut_container_of}, -}; +use lisakmod_macros::inlinec::{c_eval, cexport, cfunc, opaque_type}; + +use crate::misc::{container_of, mut_container_of}; opaque_type!( struct CKObj, -- GitLab From f34262f396e25203be1528e4fe4372a6b4139f7d Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 10:51:38 +0100 Subject: [PATCH 08/12] lisa._assets.kmodules.lisa: Split misc.rs and mem.rs --- .../kmodules/lisa/rust/lisakmod/src/lib.rs | 1 + .../kmodules/lisa/rust/lisakmod/src/mem.rs | 50 +++++++++++++++++++ .../kmodules/lisa/rust/lisakmod/src/misc.rs | 49 ------------------ .../lisa/rust/lisakmod/src/runtime/kbox.rs | 2 +- 4 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index e168175c0..7c779ff1c 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -18,6 +18,7 @@ pub mod fmt; pub mod graph; pub mod init; pub mod lifecycle; +pub mod mem; pub mod misc; pub mod prelude; pub mod registry; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs new file mode 100644 index 000000000..0cdc018c4 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +macro_rules! container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ + let ptr = $ptr; + let ptr: *const _ = (&*ptr); + let offset = core::mem::offset_of!($container, $member); + // SAFETY: Our contract is that c_kobj must be the member of a KObjectInner, so we can + // safely compute the pointer to the parent. + let container: *const $container = (ptr as *const $container).byte_sub(offset); + container + }}; +} +pub(crate) use container_of; + +#[allow(unused_macros)] +macro_rules! mut_container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ $crate::mem::container_of!($container, $member, $ptr) as *mut $container }}; +} +#[allow(unused_imports)] +pub(crate) use mut_container_of; + +macro_rules! destructure { + ($value:expr, $($field:ident),*) => {{ + // Ensure there is no duplicate in the list of fields. If there is any duplicate, the code + // will not compile as parameter names cannot be duplicated. On top of that we even get a + // nice error message about duplicated parameters for the user of the macro ! + { + #[allow(unused)] + fn check_duplicates($($field: ()),*){} + } + let value = $value; + let value = ::core::mem::MaybeUninit::new(value); + let value = ::core::mem::MaybeUninit::as_ptr(&value); + ( + $( + // SAFETY: Once the value is wrapped in MaybeUninit, no custom Drop implementation + // will run anymore, so there is no risk of a Drop implementation to read from any + // of the attributes we moved out of. + // + // We also need to ensure that we never move out of the same field twice, which was + // checked earlier by ensuring there is no duplicated in the fields list. + unsafe { + core::ptr::read(&(*value).$field) + }, + )* + ) + }} +} +pub(crate) use destructure; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs index 421106734..db0c8a295 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs @@ -1,54 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0 */ -macro_rules! container_of { - ($container:ty, $member:ident, $ptr:expr) => {{ - let ptr = $ptr; - let ptr: *const _ = (&*ptr); - let offset = core::mem::offset_of!($container, $member); - // SAFETY: Our contract is that c_kobj must be the member of a KObjectInner, so we can - // safely compute the pointer to the parent. - let container: *const $container = (ptr as *const $container).byte_sub(offset); - container - }}; -} -pub(crate) use container_of; - -#[allow(unused_macros)] -macro_rules! mut_container_of { - ($container:ty, $member:ident, $ptr:expr) => {{ $crate::misc::container_of!($container, $member, $ptr) as *mut $container }}; -} -#[allow(unused_imports)] -pub(crate) use mut_container_of; - -macro_rules! destructure { - ($value:expr, $($field:ident),*) => {{ - // Ensure there is no duplicate in the list of fields. If there is any duplicate, the code - // will not compile as parameter names cannot be duplicated. On top of that we even get a - // nice error message about duplicated parameters for the user of the macro ! - { - #[allow(unused)] - fn check_duplicates($($field: ()),*){} - } - let value = $value; - let value = ::core::mem::MaybeUninit::new(value); - let value = ::core::mem::MaybeUninit::as_ptr(&value); - ( - $( - // SAFETY: Once the value is wrapped in MaybeUninit, no custom Drop implementation - // will run anymore, so there is no risk of a Drop implementation to read from any - // of the attributes we moved out of. - // - // We also need to ensure that we never move out of the same field twice, which was - // checked earlier by ensuring there is no duplicated in the fields list. - unsafe { - core::ptr::read(&(*value).$field) - }, - )* - ) - }} -} -pub(crate) use destructure; - // Join the parameters using the given separator, avoiding any trailing separator macro_rules! join{ ($sep:expr, $first:expr $(, $rest:expr)* $(,)?) => { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs index 76b02a1bc..06049f88f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs @@ -18,7 +18,7 @@ use core::{ use lisakmod_macros::inlinec::{FfiType, FromFfi, IntoFfi, MutPtr, Opaque}; use crate::{ - misc::destructure, + mem::destructure, runtime::alloc::{GFPFlags, KernelAlloc, KmallocAllocator}, }; -- GitLab From d52ba21c2d2bab65be18dd04c9141edf66347661 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 16:02:45 +0100 Subject: [PATCH 09/12] lisa._assets.kmodules.lisa: Refactor lock traits Remove generic T parameter and turn it into a GAT. --- .../lisa/rust/lisakmod/src/runtime/sync.rs | 53 +++++++++++-------- .../lisa/rust/lisakmod/src/runtime/sysfs.rs | 2 +- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs index 61d861b45..d9545d295 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -84,38 +84,40 @@ impl Drop for LockdepClass { } } -pub trait LockGuard<'guard, T> +pub trait LockGuard<'guard> where - Self: Deref, + Self: Deref, { + type T; } -pub trait Lock +pub trait Lock where Self: Sync, { - type Guard<'a>: LockGuard<'a, T> + type T; + type Guard<'a>: LockGuard<'a, T = Self::T> where Self: 'a; fn lock(&self) -> Self::Guard<'_>; #[inline] - fn with_lock U>(&self, f: F) -> U { + fn with_lock U>(&self, f: F) -> U { f(self.lock().deref()) } } #[allow(private_bounds)] -pub trait LockMut: for<'a> _LockMut<'a, T> {} +pub trait LockMut: for<'a> _LockMut<'a> {} // TODO: We need to share the 'a lifetime between the Guard<'a> type and Self: 'a. Unfortunately, // there seems to be no direct way of doing that since a for<> lifetime only affects what comes // immediately after it. As a workaround, we can just hide that in a trait, and then use -// for<'a>_LockMut<'a, T> in the user-exposed trait. -trait _LockMut<'a, T> +// for<'a>_LockMut<'a> in the user-exposed trait. +trait _LockMut<'a> where - >::Guard<'a>: DerefMut, - Self: 'a + Lock, + ::Guard<'a>: DerefMut, + Self: 'a + Lock, { } @@ -233,14 +235,17 @@ impl Drop for SpinLockGuard<'_, T> { } } -impl<'guard, T> LockGuard<'guard, T> for SpinLockGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for SpinLockGuard<'guard, T> { + type T = T; +} unsafe impl Sync for SpinLock {} unsafe impl Send for SpinLock {} -impl Lock for SpinLock { +impl Lock for SpinLock { + type T = T; type Guard<'a> - = SpinLockGuard<'a, T> + = SpinLockGuard<'a, Self::T> where Self: 'a; @@ -262,7 +267,7 @@ impl Lock for SpinLock { } } -impl<'a, T: 'a + Send> _LockMut<'a, T> for SpinLock {} +impl<'a, T: 'a + Send> _LockMut<'a> for SpinLock {} opaque_type!(pub struct CMutex, "struct mutex", "linux/mutex.h"); @@ -424,14 +429,17 @@ impl Drop for MutexGuard<'_, T> { } } -impl<'guard, T> LockGuard<'guard, T> for MutexGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for MutexGuard<'guard, T> { + type T = T; +} unsafe impl Send for Mutex {} unsafe impl Sync for Mutex {} -impl Lock for Mutex { +impl Lock for Mutex { + type T = T; type Guard<'a> - = MutexGuard<'a, T> + = MutexGuard<'a, Self::T> where Self: 'a; @@ -451,7 +459,7 @@ impl Lock for Mutex { } } -impl<'a, T: 'a + Send> _LockMut<'a, T> for Mutex {} +impl<'a, T: 'a + Send> _LockMut<'a> for Mutex {} pub struct Rcu { // This pointer is actually a Box in disguise obtained with Box::into_raw(). We keep it as a @@ -533,7 +541,9 @@ pub struct RcuGuard<'a, T> { _phantom: PhantomData<&'a T>, } -impl<'guard, T> LockGuard<'guard, T> for RcuGuard<'guard, T> {} +impl<'guard, T> LockGuard<'guard> for RcuGuard<'guard, T> { + type T = T; +} impl Deref for RcuGuard<'_, T> { type Target = T; @@ -563,9 +573,10 @@ impl Drop for RcuGuard<'_, T> { unsafe impl Sync for Rcu {} unsafe impl Send for Rcu {} -impl Lock for Rcu { +impl Lock for Rcu { + type T = T; type Guard<'a> - = RcuGuard<'a, T> + = RcuGuard<'a, Self::T> where Self: 'a; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs index b73679341..6dcdb963f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs @@ -12,7 +12,7 @@ use core::{ use lisakmod_macros::inlinec::{c_eval, cexport, cfunc, opaque_type}; -use crate::misc::{container_of, mut_container_of}; +use crate::mem::{container_of, mut_container_of}; opaque_type!( struct CKObj, -- GitLab From f5ed7a47ec900704986735a4f97d0c1b00f0a91e Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 11:05:34 +0100 Subject: [PATCH 10/12] lisa._assets.kmodules.lisa: Support structural pinning for locks FEATURE Add a PinnedLock wrapper that allows safe structural pinning for lock types. --- .../lisa/rust/lisakmod/src/runtime/sync.rs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs index d9545d295..5d40d3eec 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -121,6 +121,69 @@ where { } +pub struct PinnedGuard<'a, L> +where + L: Lock + 'a, +{ + guard: ::Guard<'a>, +} + +impl<'a, L> PinnedGuard<'a, L> +where + L: Lock + 'a, +{ + #[inline] + pub fn as_ref(&self) -> Pin<&::T> { + // SAFETY: The guard we have can only come from a pinned lock, and that lock is hidden + // internally so that the only way to lock it is via a pinned reference. Since we do not + // allow unlocking that lock via a regular reference, it is not possible to circumvent the + // pinning guarantee and get a plain &mut T with Pin::get_ref(&Lock). + unsafe { Pin::new_unchecked(self.guard.deref()) } + } + + #[inline] + pub fn as_mut(&mut self) -> Pin<&mut ::T> + where + ::Guard<'a>: DerefMut, + { + // SAFETY: See PinnedGuard::as_ref() + unsafe { Pin::new_unchecked(self.guard.deref_mut()) } + } +} + +/// # Safety +/// +/// This trait can only be implemented for types that do not allow creating multiple locks that can +/// all be used to get an ``&mut T``. For example, ``&Mutex`` could not implement this trait, as +/// it is possible to just copy it, and such reference can then be used to gain access to a ``&mut +/// T``. +pub unsafe trait PinnableLock: Lock {} +unsafe impl PinnableLock for Mutex {} +unsafe impl PinnableLock for SpinLock {} + +pub struct PinnedLock { + inner: L, +} + +impl PinnedLock +where + L: PinnableLock, +{ + #[inline] + pub fn new(lock: L) -> PinnedLock { + PinnedLock { inner: lock } + } + + #[inline] + pub fn lock<'a>(self: Pin<&'a Self>) -> PinnedGuard<'a, L> { + // SAFETY: As per PinnableLock guarantees, there is nothing else that can leak an &mut T, + // so we can safely create a PinnedGuard that will allow getting an Pin<&mut T> + PinnedGuard { + guard: self.get_ref().inner.lock(), + } + } +} + opaque_type!(struct CSpinLock, "spinlock_t", "linux/spinlock.h"); pub struct SpinLock { -- GitLab From f5a96eed23f13cc858ad9da06227c4a866dfd9a3 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 10:52:14 +0100 Subject: [PATCH 11/12] lisa._assets.kmodules.lisa: Add FromContained trait FEATURE Allow a type to publicly implement a trait that achieves a similar purpose than container_of!() macro, without requiring the underlying field to be made public. --- .../kmodules/lisa/rust/lisakmod/src/mem.rs | 20 +++++++++++++++++++ .../lisa/rust/lisakmod/src/runtime/sync.rs | 6 +++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs index 0cdc018c4..1427bb0df 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs @@ -20,6 +20,26 @@ macro_rules! mut_container_of { #[allow(unused_imports)] pub(crate) use mut_container_of; +pub trait FromContained { + unsafe fn from_contained(contained: *const Contained) -> *const Self; +} + +macro_rules! impl_from_contained { + (($($generic:tt)*) $ty:ty, $attr:ident: $attr_ty:ty) => { + impl<$($generic)*> $crate::mem::FromContained<$attr_ty> for $ty { + unsafe fn from_contained(contained: *const $attr_ty) -> *const Self { + unsafe { + $crate::mem::container_of!( + Self, $attr, contained + ) + } + } + } + } +} +#[allow(unused_imports)] +pub(crate) use impl_from_contained; + macro_rules! destructure { ($value:expr, $($field:ident),*) => {{ // Ensure there is no duplicate in the list of fields. If there is any duplicate, the code diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs index 5d40d3eec..5171f3240 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -11,7 +11,7 @@ use core::{ use lisakmod_macros::inlinec::{cfunc, opaque_type}; -use crate::runtime::kbox::KernelKBox; +use crate::{mem::impl_from_contained, runtime::kbox::KernelKBox}; opaque_type!(struct CLockClassKey, "struct lock_class_key", "linux/lockdep.h"); @@ -165,6 +165,8 @@ pub struct PinnedLock { inner: L, } +impl_from_contained!((L) PinnedLock, inner: L); + impl PinnedLock where L: PinnableLock, @@ -433,6 +435,8 @@ impl Mutex { } } +impl_from_contained!((T) Mutex, data: T); + impl Drop for Mutex { #[inline] fn drop(&mut self) { -- GitLab From e6f0758a6d9073397ba6d2ef6aff1be42e07c30f Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 24 Mar 2025 13:33:03 +0000 Subject: [PATCH 12/12] lisa._assets.kmodules.lisa: Add workqueue bindings FEATURE --- .../lisa/rust/lisakmod/src/features/mod.rs | 31 +- .../lisa/rust/lisakmod/src/features/wq.rs | 36 ++ .../lisa/rust/lisakmod/src/runtime/wq.rs | 356 ++++++++++++++++++ 3 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs index 647d64657..a453d27fe 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs @@ -5,6 +5,7 @@ pub mod legacy; pub mod pixel6; pub mod tests; pub mod tracepoint; +pub mod wq; use alloc::{sync::Arc, vec::Vec}; use core::{ @@ -42,7 +43,35 @@ where { type Value = Arc<::Service>; } -pub type FeaturesService = typemap::TypeMap; + +pub struct FeaturesService { + map: typemap::TypeMap, +} + +impl FeaturesService { + #[inline] + fn new() -> FeaturesService { + FeaturesService { + map: typemap::TypeMap::new(), + } + } + + #[inline] + pub fn get(&self) -> Option<&::Service> + where + Feat: 'static + Feature, + { + self.map.get::().map(|service| &**service) + } + + #[inline] + pub fn insert(&mut self, service: Arc<::Service>) + where + Feat: 'static + Feature, + { + self.map.insert::(service) + } +} type LifeCycleAlias = LifeCycle::Service>, Error>; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs new file mode 100644 index 000000000..2c05a51ae --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::{sync::Arc, vec::Vec}; + +pub use crate::runtime::wq::*; +use crate::{features::define_feature, lifecycle::new_lifecycle}; + +#[derive(Debug)] +pub struct WqService { + wq: Wq, +} + +impl WqService { + fn new() -> WqService { + WqService { wq: Wq::new() } + } + + pub fn wq(&self) -> &Wq { + &self.wq + } +} + +define_feature! { + pub struct WqFeature, + name: "__wq", + visibility: Private, + Service: WqService, + Config: (), + dependencies: [], + init: |configs| { + Ok(new_lifecycle!(|services| { + yield_!(Ok(Arc::new(WqService::new()))); + Ok(()) + })) + }, +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs new file mode 100644 index 000000000..cbaaefc4c --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::boxed::Box; +use core::{ + cell::UnsafeCell, + convert::{AsMut, AsRef, Infallible}, + ffi::{CStr, c_int, c_void}, + pin::Pin, + ptr::NonNull, +}; + +use lisakmod_macros::inlinec::{cfunc, incomplete_opaque_type, opaque_type}; +use pin_project::{pin_project, pinned_drop}; + +use crate::{ + mem::{FromContained, impl_from_contained}, + runtime::sync::{Lock as _, LockdepClass, Mutex, PinnedLock}, +}; + +incomplete_opaque_type!( + struct CWq, + "struct workqueue_struct", + "linux/workqueue.h" +); + +#[derive(Debug)] +pub struct Wq { + c_wq: NonNull>, +} +unsafe impl Send for Wq {} +unsafe impl Sync for Wq {} + +impl Default for Wq { + fn default() -> Self { + Self::new() + } +} + +impl Wq { + #[inline] + pub fn new() -> Wq { + #[cfunc] + fn allo_workqueue(name: &CStr) -> Option>> { + r#" + #include + "#; + + r#" + return alloc_workqueue(name, WQ_FREEZABLE, 0); + "#; + } + + let c_wq = allo_workqueue(c"lisa").expect("Unable to allocate struct workqueue_struct"); + Wq { c_wq } + } + + #[inline] + fn c_wq(&self) -> &UnsafeCell { + unsafe { self.c_wq.as_ref() } + } +} + +impl Drop for Wq { + fn drop(&mut self) { + #[cfunc] + fn destroy_workqueue(wq: NonNull>) { + r#" + #include + "#; + + r#" + destroy_workqueue(wq); + "#; + } + destroy_workqueue(self.c_wq); + } +} + +opaque_type!( + pub struct CDelayedWork, + "struct delayed_work", + "linux/workqueue.h" +); + +#[pin_project(PinnedDrop)] +pub struct DelayedWork<'wq> { + #[pin] + c_dwork: CDelayedWork, + wq: &'wq Wq, +} +// SAFETY: DelayedWork is Send but not Sync as struct delayed_work is not Sync. Some APIs like +// queue_delayed_work() seem to be callable from any thread, but this is only partly true: the +// workqueue is designed for concurrent access, but the delayed_work itself is not protected +// against concurrent updates. The assumption of the kernel API is that only one thread will +// manipulate the delayed_work at a time. +unsafe impl<'wq> Send for DelayedWork<'wq> {} + +impl_from_contained!(('wq) DelayedWork<'wq>, c_dwork: CDelayedWork); + +impl<'wq> DelayedWork<'wq> { + #[inline] + fn enqueue(self: Pin<&mut Self>, delay: u64) { + #[cfunc] + fn enqueue( + wq: &UnsafeCell, + dwork: Pin<&mut CDelayedWork>, + delay: u64, + ) -> Result<(), c_int> { + r#" + #include + #include + "#; + + r#" + queue_delayed_work(wq, dwork, usecs_to_jiffies(delay)); + return 0; + "#; + } + let this = self.project(); + enqueue(this.wq.c_wq(), this.c_dwork, delay) + .expect("Could not initialize workqueue's delayed work"); + } + + #[inline] + fn cancel_sync(self: Pin<&mut Self>) { + #[cfunc] + fn cancel_delayed_work_sync(dwork: Pin<&mut CDelayedWork>) { + r#" + #include + "#; + + r#" + cancel_delayed_work_sync(dwork); + "# + } + cancel_delayed_work_sync(self.project().c_dwork) + } +} + +#[pinned_drop] +impl<'wq> PinnedDrop for DelayedWork<'wq> { + #[inline] + fn drop(self: Pin<&mut Self>) { + // SAFETY: We need to ensure the worker will not fire anymore and has finished running + // before we return, so we don't accidentally free the closure while it is in use. + self.cancel_sync(); + } +} + +#[pin_project] +pub struct WorkItemInner<'wq, F: 'wq + Send + Sync> { + // SAFETY: WorkItemInner _must_ be pinned as the address of __dwork will be passed around C + // APIs. + #[pin] + pub __dwork: DelayedWork<'wq>, + pub __f: F, +} + +impl_from_contained!(('wq, F: Send + Sync) WorkItemInner<'wq, F>, __dwork: DelayedWork<'wq>); + +impl<'wq, F> WorkItemInner<'wq, F> +where + F: Send + Sync, +{ + #[inline] + fn new(wq: &'wq Wq, f: F) -> Pin>> + where + F: Worker, + { + #[cfunc] + unsafe fn init_dwork( + wq: &UnsafeCell, + dwork: Pin<&mut CDelayedWork>, + worker: *const c_void, + ) -> Result<(), c_int> { + r#" + #include + "#; + + r#" + INIT_DELAYED_WORK(dwork, worker); + return 0; + "#; + } + // SAFETY: CDelayedWork does not have any specific validity invariant since it's + // essentially an opaque type. We don't want to pass it to the C API before it is moved to + // its final memory location in a Box. + let c_dwork = unsafe { CDelayedWork::new_stack(|_| Ok::<(), Infallible>(())) }.unwrap(); + let __dwork = DelayedWork { c_dwork, wq }; + let new = Box::pin(PinnedLock::new(Mutex::new( + WorkItemInner { __dwork, __f: f }, + LockdepClass::new(), + ))); + + unsafe { + init_dwork( + wq.c_wq(), + new.as_ref() + .lock() + .as_mut() + .project() + .__dwork + .project() + .c_dwork, + F::worker() as *const c_void, + ) + } + .expect("Could not initialize workqueue's delayed work"); + + new + } + + #[inline] + unsafe fn from_dwork(c_dwork: Pin<&CDelayedWork>) -> Pin<&PinnedWorkItemInner<'wq, F>> { + unsafe { + let dwork = DelayedWork::<'wq>::from_contained(c_dwork.get_ref()) + .as_ref() + .unwrap(); + let inner = WorkItemInner::<'wq, F>::from_contained(dwork) + .as_ref() + .unwrap(); + let inner = Mutex::>::from_contained(inner) + .as_ref() + .unwrap(); + let inner = PinnedLock::>>::from_contained(inner) + .as_ref() + .unwrap(); + Pin::new_unchecked(inner) + } + } +} + +type PinnedWorkItemInner<'wq, F> = PinnedLock>>; + +pub struct WorkItem<'wq, F> +where + F: Send + Sync, +{ + // We need a Mutex as the queue_delayed_work() function protects the workqueue but not the the + // dwork itself. If multiple threads try to enqueue the same dwork at the same time, or if a + // thread tries to enqueue it at the same time as it enqueues itself, there would be a race + // condition. + inner: Pin>>, +} + +impl<'wq, F> WorkItem<'wq, F> +where + F: Fn(&mut dyn AbstractWorkItem) + Send + Sync + Worker, +{ + #[inline] + pub fn __private_new(wq: &'wq Wq, f: F) -> WorkItem<'wq, F> { + WorkItem { + inner: WorkItemInner::new(wq, f), + } + } + + #[inline] + pub fn enqueue(&self, delay: u64) { + self.inner + .as_ref() + .lock() + .as_mut() + .project() + .__dwork + .enqueue(delay) + } + + #[inline] + fn cancel_sync(self: Pin<&mut Self>) { + self.inner + .as_ref() + .lock() + .as_mut() + .project() + .__dwork + .cancel_sync() + } +} + +pub trait AbstractWorkItem { + fn enqueue(&mut self, delay: u64); +} + +impl<'wq> AbstractWorkItem for Pin<&mut DelayedWork<'wq>> { + #[inline] + fn enqueue(&mut self, delay: u64) { + self.as_mut().enqueue(delay) + } +} + +incomplete_opaque_type!( + pub struct __CWorkStruct, + "struct work_struct", + "linux/workqueue.h" +); + +impl __CWorkStruct { + pub unsafe fn __to_work_item<'wq, F>(self: Pin<&Self>) -> Pin<&PinnedWorkItemInner<'wq, F>> + where + F: Send + Sync, + { + #[cfunc] + fn to_dwork(work: Pin<&__CWorkStruct>) -> Pin<&CDelayedWork> { + r#" + #include + "#; + + r#" + return to_delayed_work(CONST_CAST(struct work_struct *, work)); + "# + } + let c_dwork = to_dwork(self); + unsafe { WorkItemInner::<'wq, F>::from_dwork(c_dwork) } + } +} + +pub unsafe trait Worker { + fn worker() -> unsafe extern "C" fn(*mut __CWorkStruct); +} + +macro_rules! new_work_item { + ($wq:expr, $f:expr) => {{ + // SAFETY: We need to ensure Send and Sync for the closure, as WorkItem relies on that + // to soundly implement Send and Sync + pub type Closure = impl Fn(&mut dyn $crate::runtime::wq::AbstractWorkItem) + + ::core::marker::Send + + ::core::marker::Sync + + 'static; + let closure: Closure = $f; + + unsafe impl $crate::runtime::wq::Worker for Closure { + fn worker() -> unsafe extern "C" fn(*mut $crate::runtime::wq::__CWorkStruct) { + #[::lisakmod_macros::inlinec::cexport] + pub unsafe fn worker(c_work_struct: *mut $crate::runtime::wq::__CWorkStruct) { + // The prototype of the exported function must be exactly as the C API expects, + // otherwise we get a CFI violation. However, we know we are actually passed a + // Pin<&__CWorkStruct> + let c_work_struct = unsafe { + ::core::pin::Pin::new_unchecked( + c_work_struct.as_ref().expect("Unexpected NULL pointer"), + ) + }; + let inner = unsafe { c_work_struct.__to_work_item::() }; + let mut inner = inner.lock(); + let mut inner = inner.as_mut().project(); + let abstr: &mut dyn $crate::runtime::wq::AbstractWorkItem = &mut inner.__dwork; + (inner.__f)(abstr); + } + worker + } + } + + $crate::runtime::wq::WorkItem::__private_new($wq, closure) + }}; +} +pub(crate) use new_work_item; -- GitLab