From 678142f7bd4a0a9e5739760810888a88a88a9f79 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 7 Apr 2025 22:30:49 +0100 Subject: [PATCH 01/18] lisa._assets.kmodules.lisa: Use trait upcasting --- .../_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs index 510a90ac4..3496e775f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs @@ -8,7 +8,6 @@ use core::{ }; trait Value: Any + fmt::Debug { - fn as_any(&self) -> &dyn Any; // We need to know the key name from the trait object directly for // the Debug implementation. This way we can debug-print key/value without // knowing what is the concrete type of any of the values. @@ -40,11 +39,6 @@ where Key: ?Sized + KeyOf, >::Value: Any + fmt::Debug, { - #[inline] - fn as_any(&self) -> &dyn Any { - self - } - #[inline] fn key_name(&self) -> &str { core::any::type_name::() @@ -138,10 +132,10 @@ where { let key = TypeId::of::(); self.get_any(&key).map(|v| { - &v.as_any() + &((v as &dyn Any) .downcast_ref::>() .expect("An Any value of the wrong concrete type was inserted for that key.") - .value + .value) }) } -- GitLab From 2845fe16fa0304025bc3abf43f0e2b49f80b4044 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 7 Apr 2025 20:02:28 +0100 Subject: [PATCH 02/18] lisa._assets.kmodules.lisa: Ensure all features are Send + Sync FEATURE Ensure all features are Send + Sync so that dyn Feature is also Send + Sync, and Graph as well. --- lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a453d27fe..09eaae7cb 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs @@ -370,7 +370,7 @@ mod private { } } -pub trait Feature: private::BlanketFeature { +pub trait Feature: Send + Sync + private::BlanketFeature { // Add Self: Sized bound for all associated types so that Feature is dyn-usable. type Service: Send + Sync + Debug where -- GitLab From dd583d9c82fbb5e90216e2868accbbf8c33fa66c Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 7 Apr 2025 14:22:47 +0100 Subject: [PATCH 03/18] lisa._assets.kmodules.lisa: Do not set a global allocator when running tests FIX Fix cargo tests undefined symbols by only setting a global allocator when not building for tests. --- lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs | 1 + 1 file changed, 1 insertion(+) 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 b4833aaf7..b91a575cc 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -181,6 +181,7 @@ unsafe impl GlobalAlloc for KmallocAllocator { } } +#[cfg(not(test))] #[global_allocator] /// cbindgen:ignore static GLOBAL: KmallocAllocator<{ GFPFlags::Kernel }> = KmallocAllocator; -- GitLab From a20d32e4d58d97f1222fbc0edf5ab2a75caeeafb Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Thu, 3 Apr 2025 16:10:22 +0100 Subject: [PATCH 04/18] lisa._assets.kmodules.lisa: Use Rust 1.86 --- .../lisa/rust/lisakmod/rustc_targets/x86_64/target.json | 1 + lisa/_assets/kmodules/lisa/rust/lisakmod/src/fmt.rs | 2 +- lisa/_kmod.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json b/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json index 5efdbe352..31f059f68 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/rustc_targets/x86_64/target.json @@ -1,5 +1,6 @@ { "arch": "x86_64", + "rustc-abi": "x86-softfloat", "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float,+retpoline-external-thunk", "llvm-target": "x86_64-linux-gnu", diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/fmt.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/fmt.rs index a63f2ced3..7e6026300 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/fmt.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/fmt.rs @@ -149,7 +149,7 @@ where }; let mut new = KBox::::try_new_uninit_slice_in(new_size, *self.inner.allocator()) .map_err(|_| core::fmt::Error)?; - MaybeUninit::copy_from_slice(&mut new[..cur_size], &self.inner); + new[..cur_size].write_copy_of_slice(&self.inner); MaybeUninit::fill(&mut new[cur_size..], 0); // SAFETY: We initialized both the first part of the buffer from the old data and the extra // area with 0. diff --git a/lisa/_kmod.py b/lisa/_kmod.py index b8b2b1a39..1fc804680 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -3331,7 +3331,7 @@ class FtraceDynamicKmod(DynamicKmod): class _LISADynamicKmodSrc(KmodSrc): _RUST_SPEC = dict( - version='1.85.0', + version='1.86.0', components=[ # rust-src for -Zbuild-std 'rust-src', -- GitLab From d8f95542b032a7bdc0b267c351eaff2b7960a84b Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Mon, 31 Mar 2025 12:13:45 +0100 Subject: [PATCH 05/18] lisa._assets.kmodules.lisa: Update testing ground code --- .../kmodules/lisa/rust/lisakmod/src/init.rs | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs index 0d9afd7d7..46437b1e2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs @@ -8,22 +8,14 @@ use crate::{ }; // FIXME: clean that up -// use core::ffi::c_void; -// use core::ffi::c_ulong; -// use alloc::sync::Arc; -// use crate::features::define_feature; -// use crate::lifecycle::new_lifecycle; -// use crate::runtime::printk::pr_info; -// use alloc::vec::Vec; -// use crate::features::FeaturesService; -// use crate::features::tracepoint::TracepointFeature; + // define_feature! { // struct Feature1, // name: "feature1", // visibility: Public, // Service: (), // Config: (), -// dependencies: [Feature2, TracepointFeature], +// dependencies: [Feature2, TracepointFeature, WqFeature], // init: |configs| { // Ok(new_lifecycle!(|services| { // let services: FeaturesService = services; @@ -32,27 +24,44 @@ use crate::{ // .probe_dropper(); // pr_info!("FEATURE1 start"); // -// use crate::runtime::tracepoint::{Tracepoint, new_probe}; +// use crate::runtime::tracepoint::Tracepoint; // use core::sync::atomic::{AtomicUsize, Ordering}; // -// let x = AtomicUsize::new(0); -// let probe = new_probe!( -// &dropper, -// (_preempt: bool, _prev: *const c_void, _next:* const c_void, _prev_state: c_ulong) { -// let x = x.fetch_add(1, Ordering::SeqCst); -// crate::runtime::printk::pr_info!("SCHED_SWITCH {x}"); -// } -// ); +// // let x = AtomicUsize::new(0); +// // let probe = new_probe!( +// // &dropper, +// // (_preempt: bool, _prev: *const c_void, _next:* const c_void, _prev_state: c_ulong) { +// // let x = x.fetch_add(1, Ordering::SeqCst); +// // crate::runtime::printk::pr_info!("SCHED_SWITCH {x}"); +// // } +// // ); // // let tp = unsafe { -// Tracepoint::::lookup("sched_switch").expect("tp not found") +// Tracepoint::<(bool, *const c_void, * const c_void, c_ulong)>::lookup("sched_switch").expect("tp not found") // }; // // // let registered = tp.register_probe(&probe); // // drop(probe); // // drop(dropper); // // drop(registered); -// +// // +// +// use crate::runtime::wq; +// let wq = services.get::() +// .expect("Could not get service for WqFeature") +// .wq(); +// +// let work_item_n = AtomicUsize::new(1); +// let work_item = wq::new_work_item!(wq, move |work| { +// pr_info!("FROM WORKER"); +// let n = work_item_n.fetch_add(1, Ordering::SeqCst); +// if n < 10 { +// work.enqueue(10); +// } +// }); +// work_item.enqueue(0); +// msleep(100); +// // work_item.enqueue(0); // // // use core::ffi::CStr; @@ -65,7 +74,7 @@ use crate::{ // } // }?; // -// use crate::inlinec::cfunc; +// use lisakmod_macros::inlinec::cfunc; // #[cfunc] // fn msleep(ms: u64) { // r#" @@ -108,6 +117,7 @@ use crate::{ // // run(c"/trace-cmd start -Bmybuffer -e all > stdout.start 2>&1"); // run(c"/trace-cmd start -e all > stdout.start 2>&1"); // pr_info!("TRACING STARTED"); +// run(c"echo mymsg > /dev/kmsg"); // // f(1, c"hello", 42); // f(2, c"world", 43); @@ -123,6 +133,7 @@ use crate::{ // pr_info!("FEATURE1 stop"); // // drop(f); +// drop(work_item); // // drop(registered); // Ok(()) // })) @@ -135,21 +146,21 @@ use crate::{ // } // // define_feature! { -// struct Feature2, -// name: "feature2", -// visibility: Public, -// Service: Feature2Service, -// Config: (), -// dependencies: [], -// init: |configs| { -// Ok(new_lifecycle!(|services| { -// pr_info!("FEATURE2 start"); -// let service = Feature2Service { a: 42 }; -// yield_!(Ok(Arc::new(service))); -// pr_info!("FEATURE2 stop"); -// Ok(()) -// })) -// }, +// struct Feature2, +// name: "feature2", +// visibility: Public, +// Service: Feature2Service, +// Config: (), +// dependencies: [], +// init: |configs| { +// Ok(new_lifecycle!(|services| { +// pr_info!("FEATURE2 start"); +// let service = Feature2Service { a: 42 }; +// yield_!(Ok(Arc::new(service))); +// pr_info!("FEATURE2 stop"); +// Ok(()) +// })) +// }, // } // define_event_feature!(struct myevent); -- GitLab From e0c9af78a1d8a822ed9ed775dc0808a3b1ccd70e Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Thu, 3 Apr 2025 16:08:07 +0100 Subject: [PATCH 06/18] lisa._assets.kmodules.lisa: Add #[allow(unused_imports)] Prevent clippy from removing "pub(crate) use mymacro;" for unused macros. --- lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs | 1 + lisa/_assets/kmodules/lisa/rust/lisakmod/src/lifecycle.rs | 1 + lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs | 2 ++ lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs | 1 + .../_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs | 4 ++++ lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs | 1 + .../lisa/rust/lisakmod/src/runtime/synth_traceevent.rs | 1 + .../kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs | 1 + lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs | 1 + lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs | 1 + 10 files changed, 14 insertions(+) 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 09eaae7cb..b3e2732ef 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs @@ -576,6 +576,7 @@ macro_rules! define_feature { } }; } +#[allow(unused_imports)] pub(crate) use define_feature; #[distributed_slice] diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lifecycle.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lifecycle.rs index 9134ebad2..6712ad329 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lifecycle.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lifecycle.rs @@ -145,6 +145,7 @@ macro_rules! new_lifecycle { unsafe { funcs.new_lifecycle(closure) } }}; } +#[allow(unused_imports)] pub(crate) use new_lifecycle; enum InternalState { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs index 1427bb0df..2ca9c7c88 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/mem.rs @@ -11,6 +11,7 @@ macro_rules! container_of { container }}; } +#[allow(unused_imports)] pub(crate) use container_of; #[allow(unused_macros)] @@ -67,4 +68,5 @@ macro_rules! destructure { ) }} } +#[allow(unused_imports)] 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 db0c8a295..3bf8ba676 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/misc.rs @@ -6,4 +6,5 @@ macro_rules! join{ ::core::concat!($first $(, $sep, $rest)*) }; } +#[allow(unused_imports)] pub(crate) use join; 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 ebd457f0a..974c08ae5 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs @@ -84,6 +84,7 @@ macro_rules! __pr_level { ).expect("Could not write to dmesg") }} } +#[allow(unused_imports)] pub(crate) use __pr_level; macro_rules! pr_debug { @@ -94,6 +95,7 @@ macro_rules! pr_debug { ) }} } +#[allow(unused_imports)] pub(crate) use pr_debug; macro_rules! pr_info { @@ -104,6 +106,7 @@ macro_rules! pr_info { ) }} } +#[allow(unused_imports)] pub(crate) use pr_info; macro_rules! pr_err { @@ -114,4 +117,5 @@ macro_rules! pr_err { ) }} } +#[allow(unused_imports)] pub(crate) use pr_err; 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 5171f3240..174db95e4 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -378,6 +378,7 @@ macro_rules! new_static_mutex { }); }; } +#[allow(unused_imports)] pub(crate) use new_static_mutex; pub struct Mutex { 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 c1480b6ff..747e146cd 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 @@ -296,4 +296,5 @@ macro_rules! new_event { } }; } +#[allow(unused_imports)] 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 ead73e580..452701b3c 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs @@ -78,4 +78,5 @@ macro_rules! new_event { } }}; } +#[allow(unused_imports)] pub(crate) use new_event; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs index cbaaefc4c..cfda58ad9 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs @@ -353,4 +353,5 @@ macro_rules! new_work_item { $crate::runtime::wq::WorkItem::__private_new($wq, closure) }}; } +#[allow(unused_imports)] pub(crate) use new_work_item; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs index 3496e775f..3b1228990 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/typemap.rs @@ -182,6 +182,7 @@ macro_rules! make_index { impl $trait for $ty {} }; } +#[allow(unused_imports)] pub(crate) use make_index; macro_rules! add_index_key { -- GitLab From 3125b2a12842015a5794af07b73498445a1688af Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 22:32:52 +0100 Subject: [PATCH 07/18] lisa._assets,kmodules.lisa: Add cpp!() macro FEATURE Allow evaluating at compile time a boolean C preprocessor expression. --- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 46df4b1f7..43cd7df03 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -1399,3 +1399,27 @@ macro_rules! __internal_c_eval { }}; } pub use crate::__internal_c_eval as c_eval; + + +#[macro_export] +macro_rules! __internal_cpp { + ($cpp_expr:literal $(, $c_header:literal)*) => {{ + let x: Option = $crate::inlinec::cconstant!( + ( + $( + "#include \"", $c_header, "\"\n" + ),* + ), + ( + "\n#if (", $cpp_expr, ")\n1\n#else\n0\n#endif\n", + ) + ); + match x { + Some(x) => x != 0, + None => true, + } + }}; +} +pub use crate::__internal_cpp as cpp; + + -- GitLab From 280792142d0fb102551f6e3b37c811a7882cd1d9 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 22:37:13 +0100 Subject: [PATCH 08/18] lisa._assets.kmodules.lisa: Rename c_eval!() to ceval!() To be consistent with other macros, rename c_eval!() to ceval!() --- .../kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs | 7 ++----- .../kmodules/lisa/rust/lisakmod/src/features/tests.rs | 6 +++--- lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs | 2 +- .../kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs | 4 ++-- 4 files changed, 8 insertions(+), 11 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 43cd7df03..833fd87cb 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -1385,7 +1385,7 @@ macro_rules! __internal_c_static_assert { pub use crate::__internal_c_static_assert as c_static_assert; #[macro_export] -macro_rules! __internal_c_eval { +macro_rules! __internal_ceval { ($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. @@ -1398,8 +1398,7 @@ macro_rules! __internal_c_eval { snippet() }}; } -pub use crate::__internal_c_eval as c_eval; - +pub use crate::__internal_ceval as ceval; #[macro_export] macro_rules! __internal_cpp { @@ -1421,5 +1420,3 @@ macro_rules! __internal_cpp { }}; } pub use crate::__internal_cpp as cpp; - - 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 3d7f3267c..799c51d2f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs @@ -3,7 +3,7 @@ use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; use core::ffi::CStr; -use lisakmod_macros::inlinec::{c_eval, cconstant, cfunc}; +use lisakmod_macros::inlinec::{cconstant, ceval, cfunc}; use crate::{ error::Error, @@ -57,7 +57,7 @@ test! { test! { test3, { - let minalign = c_eval!("linux/slab.h", "ARCH_KMALLOC_MINALIGN", usize); + let minalign = ceval!("linux/slab.h", "ARCH_KMALLOC_MINALIGN", usize); // Check we don't get any C compilation error with duplicated code. let minalign2: usize = cconstant!("#include ", "ARCH_KMALLOC_MINALIGN").unwrap(); assert_eq!(minalign, minalign2); @@ -187,7 +187,7 @@ test! { } { - let zst_addr = c_eval!("linux/slab.h", "ZERO_SIZE_PTR", *const u8); + let zst_addr = ceval!("linux/slab.h", "ZERO_SIZE_PTR", *const u8); let b = KernelKBox::new(()); assert_eq!(b.as_ptr() as usize, zst_addr as usize); drop(b); diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs index f5c379369..828f3fcec 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs @@ -3,7 +3,7 @@ #[allow(unused_imports)] pub(crate) use ::alloc::boxed::Box; #[allow(unused_imports)] -pub(crate) use lisakmod_macros::inlinec::{c_eval, cconstant, cexport, cfunc, cstatic}; +pub(crate) use lisakmod_macros::inlinec::{cconstant, ceval, cexport, cfunc, cstatic}; #[allow(unused_imports)] pub(crate) use crate::{ 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 6dcdb963f..9a7b5a068 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs @@ -10,7 +10,7 @@ use core::{ pin::Pin, }; -use lisakmod_macros::inlinec::{c_eval, cexport, cfunc, opaque_type}; +use lisakmod_macros::inlinec::{ceval, cexport, cfunc, opaque_type}; use crate::mem::{container_of, mut_container_of}; @@ -371,7 +371,7 @@ impl KObject { } #[inline] pub fn sysfs_module_root() -> Self { - let c_kobj = c_eval!("linux/kobject.h", "&THIS_MODULE->mkobj.kobj", *mut CKObj); + let c_kobj = ceval!("linux/kobject.h", "&THIS_MODULE->mkobj.kobj", *mut CKObj); let inner = KObjectInner::from_c_kobj(c_kobj); // SAFETY: We assume that the pointer we got is valid as it is comming from a well-known // kernel API. -- GitLab From b55eefbe20876c9d5eebbf0c4320c7c9c7060b8d Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Thu, 3 Apr 2025 11:41:44 +0100 Subject: [PATCH 09/18] lisa._assets.kmodules.lisa: Add ERR_PTR() bindings FEATURE --- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 121 +++++++++++++++++- 1 file changed, 117 insertions(+), 4 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 833fd87cb..78f479483 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -5,7 +5,9 @@ use core::{ alloc::Layout, cell::UnsafeCell, convert::Infallible, + error::Error as StdError, ffi::{CStr, c_char, c_int, c_uchar, c_void}, + fmt, mem::MaybeUninit, ops::Deref, pin::Pin, @@ -124,8 +126,15 @@ impl_ptr!(*const T, ConstPtr, &'a T); impl From> for *const T { #[inline] - fn from(x: ConstPtr) -> *const T { - x.0 + fn from(ptr: ConstPtr) -> *const T { + ptr.0 + } +} + +impl From<*const T> for ConstPtr { + #[inline] + fn from(ptr: *const T) -> ConstPtr { + ConstPtr(ptr) } } @@ -170,8 +179,15 @@ impl_ptr!(*mut T, MutPtr, &'a mut T); impl From> for *mut T { #[inline] - fn from(x: MutPtr) -> *mut T { - x.0 + fn from(ptr: MutPtr) -> *mut T { + ptr.0 + } +} + +impl From<*mut T> for MutPtr { + #[inline] + fn from(ptr: *mut T) -> MutPtr { + MutPtr(ptr) } } @@ -1035,6 +1051,103 @@ impl FromFfi for Result<(), Infallible> { } } +#[derive(Debug)] +pub enum PtrError { + Code(usize), + Null, +} + +impl fmt::Display for PtrError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PtrError::Null => f.write_str("pointer is NULL"), + PtrError::Code(code) => write!(f, "error code: {code}"), + } + } +} + +impl StdError for PtrError { + #[inline] + fn source(&self) -> Option<&(dyn StdError + 'static)> { + None + } +} + +impl PtrError { + // We need Sized as we need *mut T to be a thin pointer type, as we cannot build fat pointers + // out of thin air. + pub fn into_ptr(self) -> *mut T { + #[cfunc] + fn err_ptr(code: usize) -> *mut c_void { + r#" + #include + "#; + + r#" + return ERR_PTR(code); + "# + } + match self { + PtrError::Code(code) => err_ptr(code) as *mut T, + PtrError::Null => core::ptr::null_mut(), + } + } + + pub fn from_ptr(ptr: *mut T) -> Result, PtrError> { + #[cfunc] + fn ptr_err_or_zero(ptr: *mut c_void) -> usize { + r#" + #include + "#; + + r#" + return PTR_ERR_OR_ZERO(ptr); + "# + } + if ptr.is_null() { + Err(PtrError::Null) + } else { + match ptr_err_or_zero(ptr as *mut c_void) { + 0 => Ok(NonNull::new(ptr).unwrap()), + err => Err(PtrError::Code(err)), + } + } + } +} + +impl FfiType for Result, PtrError> +where + *mut T: FfiType, +{ + const C_TYPE: &'static str = <*mut T as FfiType>::C_TYPE; + const C_HEADER: Option<&'static str> = <*mut T as FfiType>::C_HEADER; + type FfiType = <*mut T as FfiType>::FfiType; +} + +impl IntoFfi for Result, PtrError> +where + *mut T: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + Ok(ptr) => ptr.as_ptr().into_ffi(), + Err(err) => err.into_ptr::().into_ffi(), + } + } +} + +impl FromFfi for Result, PtrError> +where + *mut T: FfiType + FromFfi, +{ + #[inline] + unsafe fn from_ffi(ptr: Self::FfiType) -> Self { + let ptr: *mut T = unsafe { FromFfi::from_ffi(ptr) }; + PtrError::from_ptr(ptr) + } +} + // Combine both alignment and size so that the resulting type can be used in a repr(C) parent // struct. Otherwise, we would end up having to either: // * Use a ZST for alignment, which cannot be repr(C) as of 25/11/2024 -- GitLab From 15502af1ed8d4adbeac580da2b3f4adde82738fa Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Thu, 3 Apr 2025 12:44:30 +0100 Subject: [PATCH 10/18] lisa._assets.kmodules.lisa: FFI bindings for negative errors FEATURE Implement FFI bindings for Result to model the common pattern of C functions returning a positive value, or a negative error. --- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) 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 78f479483..39fdc63b7 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -4,7 +4,7 @@ use alloc::{boxed::Box, sync::Arc}; use core::{ alloc::Layout, cell::UnsafeCell, - convert::Infallible, + convert::{Infallible, TryFrom}, error::Error as StdError, ffi::{CStr, c_char, c_int, c_uchar, c_void}, fmt, @@ -1051,6 +1051,90 @@ impl FromFfi for Result<(), Infallible> { } } +pub trait Unsigned +where + Self: TryInto, + Self::Signed: TryInto, +{ + type Signed; + const SIGNED_ZERO: Self::Signed; +} + +macro_rules! impl_unsigned { + ($unsigned:ty, $signed:ty) => { + impl Unsigned for $unsigned { + type Signed = $signed; + const SIGNED_ZERO: Self::Signed = 0; + } + }; +} + +impl_unsigned!(usize, isize); +impl_unsigned!(u64, i64); +impl_unsigned!(u32, i32); +impl_unsigned!(u16, i16); +impl_unsigned!(u8, i8); + +#[derive(Debug, Clone, Copy)] +pub struct NegativeError { + pub code: T::Signed, +} + +impl fmt::Display for NegativeError +where + T: Unsigned, + ::Signed: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code) + } +} + +impl FfiType for Result> +where + T: Unsigned, + ::Signed: FfiType, +{ + const C_TYPE: &'static str = <::Signed as FfiType>::C_TYPE; + const C_HEADER: Option<&'static str> = <::Signed as FfiType>::C_HEADER; + type FfiType = <::Signed as FfiType>::FfiType; +} + +impl IntoFfi for Result> +where + T: Unsigned, + ::Signed: FfiType + IntoFfi + fmt::Debug, + ::Signed>>::Error: fmt::Debug, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + Ok(val) => val + .try_into() + .expect("Unsigned value overflowed") + .into_ffi(), + Err(err) => err.code.into_ffi(), + } + } +} + +impl FromFfi for Result> +where + T: Unsigned, + ::Signed: FfiType + FromFfi + TryFrom + PartialOrd, + <::Signed as TryInto>::Error: fmt::Debug, +{ + #[inline] + unsafe fn from_ffi(val: Self::FfiType) -> Self { + let val = unsafe { ::Signed::from_ffi(val) }; + if val < ::SIGNED_ZERO { + Err(NegativeError { code: val }) + } else { + Ok(val.try_into().unwrap()) + } + } +} + #[derive(Debug)] pub enum PtrError { Code(usize), -- GitLab From 0e08dc93b4d03ab0e1a2b35de519b85d5b6f424c Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:13:50 +0100 Subject: [PATCH 11/18] lisa._assets.kmodules.lisa: Improve FFI bindings FEATURE * Implement IntoFfi for &str. This is achieved by using struct rust_str rather than using const char*, as the latter only allowed a FromFfi implementation. * Pretty print error codes wrapper in ErrorCode. This includes the NegativeError type that provides a binding to the PTR_ERR() kernel API. --- .../kmodules/lisa/rust/lisakmod-macros/cffi.h | 8 +- .../lisa/rust/lisakmod-macros/src/inlinec.rs | 355 +++++++++++++++--- .../lisa/rust/lisakmod/src/features/tests.rs | 19 +- .../rust/lisakmod/src/runtime/tracepoint.rs | 6 +- 4 files changed, 326 insertions(+), 62 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h index 1b9a0ef36..856c56f6d 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h @@ -43,16 +43,16 @@ #endif #define __make_slice_ty(name, ty) \ - struct slice_##name { \ + struct name { \ __typeof__(ty) *data; \ size_t len; \ } __no_randomize_layout; \ - struct slice_const_##name { \ + struct const_##name { \ const __typeof__(ty) *data; \ const size_t len; \ } __no_randomize_layout; -__make_slice_ty(u8, uint8_t) -__make_slice_ty(rust_str, struct slice_u8) +__make_slice_ty(slice_u8, uint8_t) +__make_slice_ty(rust_str, char) #endif /* _CFFI_H */ 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 39fdc63b7..df1b4ce77 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -4,7 +4,7 @@ use alloc::{boxed::Box, sync::Arc}; use core::{ alloc::Layout, cell::UnsafeCell, - convert::{Infallible, TryFrom}, + convert::Infallible, error::Error as StdError, ffi::{CStr, c_char, c_int, c_uchar, c_void}, fmt, @@ -722,13 +722,6 @@ impl NullPtr for T { } } -impl NullPtr for [T] { - #[inline] - fn null_mut() -> *mut Self { - core::ptr::slice_from_raw_parts_mut(null_mut(), 0) - } -} - impl FfiType for Option> where T: ?Sized, @@ -866,32 +859,47 @@ impl FromFfi for &CStr { } } -// No IntoFfi instance for &str as we cannot provide NULL-terminated string out of an &str. Use -// &CStr for that. - -impl<'a> FfiType for Option<&'a str> { - const C_TYPE: &'static str = <&'a CStr as FfiType>::C_TYPE; - const C_HEADER: Option<&'static str> = <&'a CStr as FfiType>::C_HEADER; - type FfiType = <&'a CStr as FfiType>::FfiType; +impl FfiType for Option<&str> { + const C_TYPE: &'static str = "struct const_rust_str"; + const C_HEADER: Option<&'static str> = Some("rust/lisakmod-macros/cffi.h"); + type FfiType = FfiSlice<*const u8>; } impl FromFfi for Option<&str> { #[inline] unsafe fn from_ffi(x: Self::FfiType) -> Self { - unsafe { - Some( - >::from_ffi(x)? - .to_str() - .expect("Invalid UTF-8 content in C string"), - ) + let slice = if x.data.is_null() { + None + } else { + Some(unsafe { core::slice::from_raw_parts(x.data, x.len) }) + }?; + Some(core::str::from_utf8(slice).expect("Invalid UTF-8 content in C string")) + } +} + +impl IntoFfi for Option<&str> { + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + None => FfiSlice { + data: null(), + len: 0, + }, + Some(s) => { + let buf = s.as_bytes(); + Self::FfiType { + data: buf.as_ptr(), + len: buf.len(), + } + } } } } impl<'a> FfiType for &'a str { - const C_TYPE: &'static str = <&'a CStr as FfiType>::C_TYPE; - const C_HEADER: Option<&'static str> = <&'a CStr as FfiType>::C_HEADER; - type FfiType = <&'a CStr as FfiType>::FfiType; + const C_TYPE: &'static str = as FfiType>::C_TYPE; + const C_HEADER: Option<&'static str> = as FfiType>::C_HEADER; + type FfiType = as FfiType>::FfiType; } impl FromFfi for &str { @@ -904,6 +912,13 @@ impl FromFfi for &str { } } +impl IntoFfi for &str { + #[inline] + fn into_ffi(self) -> Self::FfiType { + Some(self).into_ffi() + } +} + trait Pointer { type Pointee; } @@ -935,6 +950,31 @@ macro_rules! impl_slice { ($ty:ty, $c_name_const:literal, $c_name_mut:literal, $c_header:expr) => { const _: () = { type Type<'a> = $ty; + + impl<'a> FfiType for ConstPtr>> { + const C_TYPE: &'static str = concat!("const __typeof__(", $c_name_const, ") *"); + const C_HEADER: Option<&'static str> = Some($c_header); + type FfiType = *const FfiSlice<*const Type<'a>>; + } + + impl<'a> FfiType for ConstPtr>> { + const C_TYPE: &'static str = concat!("const __typeof__(", $c_name_const, ") *"); + const C_HEADER: Option<&'static str> = Some($c_header); + type FfiType = *const FfiSlice<*mut Type<'a>>; + } + + impl<'a> FfiType for MutPtr>> { + const C_TYPE: &'static str = concat!("__typeof__(", $c_name_const, ") *"); + const C_HEADER: Option<&'static str> = Some($c_header); + type FfiType = *mut FfiSlice<*const Type<'a>>; + } + + impl<'a> FfiType for MutPtr>> { + const C_TYPE: &'static str = concat!("__typeof__(", $c_name_const, ") *"); + const C_HEADER: Option<&'static str> = Some($c_header); + type FfiType = *mut FfiSlice<*mut Type<'a>>; + } + impl<'a> FfiType for ConstPtr<[Type<'a>]> { const C_TYPE: &'static str = $c_name_const; const C_HEADER: Option<&'static str> = Some($c_header); @@ -952,18 +992,11 @@ macro_rules! impl_slice { impl_slice!( u8, - "struct slice_const_u8", + "struct const_slice_u8", "struct slice_u8", "rust/lisakmod-macros/cffi.h" ); -impl_slice!( - &'a str, - "struct slice_const_rust_str", - "struct slice_rust_str", - "rust/lisakmod-macros/cffi.h" -); - impl IntoFfi for ConstPtr<[T]> where ConstPtr<[T]>: FfiType>, @@ -984,7 +1017,7 @@ where { #[inline] unsafe fn from_ffi(x: Self::FfiType) -> Self { - ConstPtr(core::ptr::slice_from_raw_parts(x.data, x.len)) + ConstPtr(unsafe { core::slice::from_raw_parts(x.data, x.len) }) } } @@ -1008,7 +1041,7 @@ where { #[inline] unsafe fn from_ffi(x: Self::FfiType) -> Self { - MutPtr(core::ptr::slice_from_raw_parts_mut(x.data, x.len)) + MutPtr(unsafe { core::slice::from_raw_parts_mut(x.data, x.len) }) } } @@ -1051,6 +1084,10 @@ impl FromFfi for Result<(), Infallible> { } } +pub trait NumericAbs { + fn unsigned_abs(&self) -> u64; +} + pub trait Unsigned where Self: TryInto, @@ -1066,6 +1103,13 @@ macro_rules! impl_unsigned { type Signed = $signed; const SIGNED_ZERO: Self::Signed = 0; } + + impl NumericAbs for $signed { + #[inline] + fn unsigned_abs(&self) -> u64 { + (*self).unsigned_abs().try_into().unwrap() + } + } }; } @@ -1075,18 +1119,52 @@ impl_unsigned!(u32, i32); impl_unsigned!(u16, i16); impl_unsigned!(u8, i8); -#[derive(Debug, Clone, Copy)] -pub struct NegativeError { - pub code: T::Signed, +pub struct NegativeError +where + T: Unsigned, +{ + err: ErrorCode<::Signed>, +} + +impl Clone for NegativeError +where + T: Unsigned, + ::Signed: Clone, +{ + #[inline] + fn clone(&self) -> Self { + NegativeError { + err: self.err.clone(), + } + } +} + +impl Copy for NegativeError +where + T: Unsigned, + ::Signed: Copy, +{ +} + +impl fmt::Debug for NegativeError +where + T: Unsigned, + ::Signed: fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.err, f) + } } impl fmt::Display for NegativeError where T: Unsigned, - ::Signed: fmt::Display, + ::Signed: fmt::Display + NumericAbs, { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.code) + fmt::Display::fmt(&self.err, f) } } @@ -1103,7 +1181,7 @@ where impl IntoFfi for Result> where T: Unsigned, - ::Signed: FfiType + IntoFfi + fmt::Debug, + ::Signed: IntoFfi, ::Signed>>::Error: fmt::Debug, { #[inline] @@ -1113,7 +1191,7 @@ where .try_into() .expect("Unsigned value overflowed") .into_ffi(), - Err(err) => err.code.into_ffi(), + Err(err) => err.err.code.into_ffi(), } } } @@ -1121,23 +1199,199 @@ where impl FromFfi for Result> where T: Unsigned, - ::Signed: FfiType + FromFfi + TryFrom + PartialOrd, + ::Signed: FromFfi + PartialOrd, <::Signed as TryInto>::Error: fmt::Debug, { #[inline] unsafe fn from_ffi(val: Self::FfiType) -> Self { let val = unsafe { ::Signed::from_ffi(val) }; if val < ::SIGNED_ZERO { - Err(NegativeError { code: val }) + Err(NegativeError { + err: ErrorCode::new(val), + }) } else { Ok(val.try_into().unwrap()) } } } +#[derive(Debug, Clone, Copy)] +pub struct ErrorCode { + code: T, +} + +impl ErrorCode { + #[inline] + pub fn new(code: T) -> ErrorCode { + ErrorCode { code } + } +} + +impl fmt::Display for ErrorCode +where + T: NumericAbs + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn strerror(code: u64) -> Option<&'static str> { + #[cfunc] + fn strerror(code: u64) -> Option<&'static CStr> { + r#" + #include + "#; + + r#" + switch (code) { + // Basic set of errors from include/uapi/asm-generic/errno-base.h + case EPERM : return "Operation not permitted"; + case ENOENT : return "No such file or directory"; + case ESRCH : return "No such process"; + case EINTR : return "Interrupted system call"; + case EIO : return "I/O error"; + case ENXIO : return "No such device or address"; + case E2BIG : return "Argument list too long"; + case ENOEXEC : return "Exec format error"; + case EBADF : return "Bad file number"; + case ECHILD : return "No child processes"; + case EAGAIN : return "Try again"; + case ENOMEM : return "Out of memory"; + case EACCES : return "Permission denied"; + case EFAULT : return "Bad address"; + case ENOTBLK : return "Block device required"; + case EBUSY : return "Device or resource busy"; + case EEXIST : return "File exists"; + case EXDEV : return "Cross-device link"; + case ENODEV : return "No such device"; + case ENOTDIR : return "Not a directory"; + case EISDIR : return "Is a directory"; + case EINVAL : return "Invalid argument"; + case ENFILE : return "File table overflow"; + case EMFILE : return "Too many open files"; + case ENOTTY : return "Not a typewriter"; + case ETXTBSY : return "Text file busy"; + case EFBIG : return "File too large"; + case ENOSPC : return "No space left on device"; + case ESPIPE : return "Illegal seek"; + case EROFS : return "Read-only file system"; + case EMLINK : return "Too many links"; + case EPIPE : return "Broken pipe"; + case EDOM : return "Math argument out of domain of func"; + case ERANGE : return "Math result not representable"; + + // Full set of errors from include/uapi/asm-generic/errno.h + case EDEADLK : return "Resource deadlock would occur"; + case ENAMETOOLONG: return "File name too long"; + case ENOLCK : return "No record locks available"; + case ENOSYS : return "Invalid system call number"; + case ENOTEMPTY : return "Directory not empty"; + case ELOOP : return "Too many symbolic links encountered"; + case ENOMSG : return "No message of desired type"; + case EIDRM : return "Identifier removed"; + case ECHRNG : return "Channel number out of range"; + case EL2NSYNC : return "Level 2 not synchronized"; + case EL3HLT : return "Level 3 halted"; + case EL3RST : return "Level 3 reset"; + case ELNRNG : return "Link number out of range"; + case EUNATCH : return "Protocol driver not attached"; + case ENOCSI : return "No CSI structure available"; + case EL2HLT : return "Level 2 halted"; + case EBADE : return "Invalid exchange"; + case EBADR : return "Invalid request descriptor"; + case EXFULL : return "Exchange full"; + case ENOANO : return "No anode"; + case EBADRQC : return "Invalid request code"; + case EBADSLT : return "Invalid slot"; + case EBFONT : return "Bad font file format"; + case ENOSTR : return "Device not a stream"; + case ENODATA : return "No data available"; + case ETIME : return "Timer expired"; + case ENOSR : return "Out of streams resources"; + case ENONET : return "Machine is not on the network"; + case ENOPKG : return "Package not installed"; + case EREMOTE : return "Object is remote"; + case ENOLINK : return "Link has been severed"; + case EADV : return "Advertise error"; + case ESRMNT : return "Srmount error"; + case ECOMM : return "Communication error on send"; + case EPROTO : return "Protocol error"; + case EMULTIHOP : return "Multihop attempted"; + case EDOTDOT : return "RFS specific error"; + case EBADMSG : return "Not a data message"; + case EOVERFLOW : return "Value too large for defined data type"; + case ENOTUNIQ : return "Name not unique on network"; + case EBADFD : return "File descriptor in bad state"; + case EREMCHG : return "Remote address changed"; + case ELIBACC : return "Can not access a needed shared library"; + case ELIBBAD : return "Accessing a corrupted shared library"; + case ELIBSCN : return ".lib section in a.out corrupted"; + case ELIBMAX : return "Attempting to link in too many shared libraries"; + case ELIBEXEC : return "Cannot exec a shared library directly"; + case EILSEQ : return "Illegal byte sequence"; + case ERESTART : return "Interrupted system call should be restarted"; + case ESTRPIPE : return "Streams pipe error"; + case EUSERS : return "Too many users"; + case ENOTSOCK : return "Socket operation on non-socket"; + case EDESTADDRREQ: return "Destination address required"; + case EMSGSIZE : return "Message too long"; + case EPROTOTYPE: return "Protocol wrong type for socket"; + case ENOPROTOOPT: return "Protocol not available"; + case EPROTONOSUPPORT: return "Protocol not supported"; + case ESOCKTNOSUPPORT: return "Socket type not supported"; + case EOPNOTSUPP: return "Operation not supported on transport endpoint"; + case EPFNOSUPPORT: return "Protocol family not supported"; + case EAFNOSUPPORT: return "Address family not supported by protocol"; + case EADDRINUSE: return "Address already in use"; + case EADDRNOTAVAIL: return "Cannot assign requested address"; + case ENETDOWN : return "Network is down"; + case ENETUNREACH: return "Network is unreachable"; + case ENETRESET : return "Network dropped connection because of reset"; + case ECONNABORTED: return "Software caused connection abort"; + case ECONNRESET: return "Connection reset by peer"; + case ENOBUFS : return "No buffer space available"; + case EISCONN : return "Transport endpoint is already connected"; + case ENOTCONN : return "Transport endpoint is not connected"; + case ESHUTDOWN : return "Cannot send after transport endpoint shutdown"; + case ETOOMANYREFS: return "Too many references: cannot splice"; + case ETIMEDOUT : return "Connection timed out"; + case ECONNREFUSED: return "Connection refused"; + case EHOSTDOWN : return "Host is down"; + case EHOSTUNREACH: return "No route to host"; + case EALREADY : return "Operation already in progress"; + case EINPROGRESS: return "Operation now in progress"; + case ESTALE : return "Stale file handle"; + case EUCLEAN : return "Structure needs cleaning"; + case ENOTNAM : return "Not a XENIX named type file"; + case ENAVAIL : return "No XENIX semaphores available"; + case EISNAM : return "Is a named type file"; + case EREMOTEIO : return "Remote I/O error"; + case EDQUOT : return "Quota exceeded"; + case ENOMEDIUM : return "No medium found"; + case EMEDIUMTYPE: return "Wrong medium type"; + case ECANCELED : return "Operation Canceled"; + case ENOKEY : return "Required key not available"; + case EKEYEXPIRED: return "Key has expired"; + case EKEYREVOKED: return "Key has been revoked"; + case EKEYREJECTED: return "Key was rejected by service"; + case EOWNERDEAD: return "Owner died"; + case ENOTRECOVERABLE: return "State not recoverable"; + case ERFKILL : return "Operation not possible due to RF-kill"; + case EHWPOISON : return "Memory page has hardware error"; + default: return NULL; + } + "#; + } + strerror(code).map(|s| s.to_str().expect("Invalid UTF-8")) + } + + match strerror(self.code.unsigned_abs()) { + Some(err) => write!(f, "{err}"), + None => write!(f, "error code: {}", self.code), + } + } +} + #[derive(Debug)] pub enum PtrError { - Code(usize), + Code(ErrorCode), Null, } @@ -1145,7 +1399,7 @@ impl fmt::Display for PtrError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PtrError::Null => f.write_str("pointer is NULL"), - PtrError::Code(code) => write!(f, "error code: {code}"), + PtrError::Code(err) => write!(f, "{err}"), } } } @@ -1162,7 +1416,7 @@ impl PtrError { // out of thin air. pub fn into_ptr(self) -> *mut T { #[cfunc] - fn err_ptr(code: usize) -> *mut c_void { + fn err_ptr(code: isize) -> *mut c_void { r#" #include "#; @@ -1172,14 +1426,14 @@ impl PtrError { "# } match self { - PtrError::Code(code) => err_ptr(code) as *mut T, + PtrError::Code(err) => err_ptr(err.code) as *mut T, PtrError::Null => core::ptr::null_mut(), } } pub fn from_ptr(ptr: *mut T) -> Result, PtrError> { #[cfunc] - fn ptr_err_or_zero(ptr: *mut c_void) -> usize { + fn ptr_err_or_zero(ptr: *mut c_void) -> isize { r#" #include "#; @@ -1193,7 +1447,7 @@ impl PtrError { } else { match ptr_err_or_zero(ptr as *mut c_void) { 0 => Ok(NonNull::new(ptr).unwrap()), - err => Err(PtrError::Code(err)), + err => Err(PtrError::Code(ErrorCode::new(err))), } } } @@ -1348,7 +1602,8 @@ macro_rules! __internal_opaque_type { // * A way to get the correct alignment using C compile-time reflection. // * repr(transparent), so that the FFI-safe warning is satisfied in all use cases. This // way, we guarantee that the struct has only one non-ZST member, and its ABI is that of - // this member. The member in question is an array of u8, which is FFI-safe. + // this member. The member in question is an array of u8, which is FFI-safe and has no + // niche. #[repr(transparent)] $vis struct $name { // Since we cannot make opaque types aligned with a simple attribute 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 799c51d2f..42e139771 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs @@ -82,15 +82,22 @@ test! { #[cfunc] unsafe fn my_cfunc_3(x: &CStr) -> &str { - "return x;" + r#" + #include + "#; + "return (struct const_rust_str){.data = x, .len = strlen(x)};" } assert_eq!(unsafe { my_cfunc_3(c"hello") }, "hello"); #[cfunc] fn my_cfunc_4() -> &'static str { + r#" + #include + "#; + r#" static const char *mystring = "hello world"; - return mystring; + return (struct const_rust_str){.data = mystring, .len = strlen(mystring)}; "# } assert_eq!(my_cfunc_4(), "hello world"); @@ -125,7 +132,7 @@ test! { fn my_cfunc_8() -> Option<&'static str> { r#" static const char *mystring = "hello world"; - return mystring; + return (struct const_rust_str){.data = mystring, .len = strlen(mystring)}; "# } assert_eq!(my_cfunc_8(), Some("hello world")); @@ -133,7 +140,7 @@ test! { #[cfunc] fn my_cfunc_9() -> Option<&'static str> { r#" - return NULL; + return (struct const_rust_str){.data = NULL}; "# } assert_eq!(my_cfunc_9(), None); @@ -141,7 +148,7 @@ test! { #[cfunc] unsafe fn my_cfunc_10<'a>() -> Option<&'a str> { r#" - return NULL; + return (struct const_rust_str){.data = NULL}; "# } assert_eq!(unsafe { my_cfunc_10() }, None); @@ -159,7 +166,7 @@ test! { fn my_cfunc_12() -> &'static [u8] { r#" unsigned char *s = "hello world"; - struct slice_const_u8 x = {.data = s, .len = strlen(s)}; + struct const_slice_u8 x = {.data = s, .len = strlen(s)}; return x; "# } 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 190edb9f2..c1f7bf9c5 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/tracepoint.rs @@ -103,7 +103,7 @@ impl<'tp, Args> Tracepoint<'tp, Args> { // safely materialize for CTracepoint as there may be concurrent users of the struct. pub fn name<'a>(&'a self) -> &'a str { #[cfunc] - fn name<'a>(tp: *const CTracepoint) -> Option<&'a str> { + fn name<'a>(tp: *const CTracepoint) -> Option<&'a CStr> { r#" #include "#; @@ -112,7 +112,9 @@ impl<'tp, Args> Tracepoint<'tp, Args> { return tp->name; "# } - name::<'a>(self.c_tp.get()).unwrap_or("") + name::<'a>(self.c_tp.get()) + .map(|s| s.to_str().expect("Invalid UTF-8 in C string")) + .unwrap_or("") } pub fn register_probe<'probe>( -- GitLab From 1fda06b7dbe2f537278c4b20ccf54feb3bd93d83 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 2 Apr 2025 16:47:19 +0100 Subject: [PATCH 12/18] lisa._assets.kmodules.lisa: Add fs bindings FEATURE Allow reading and writing files. --- lisa/_assets/kmodules/lisa/pixel6.c | 4 +- .../kmodules/lisa/rust/lisakmod/Cargo.lock | 7 + .../kmodules/lisa/rust/lisakmod/Cargo.toml | 1 + .../kmodules/lisa/rust/lisakmod/src/error.rs | 7 + .../lisa/rust/lisakmod/src/runtime/alloc.rs | 5 + .../lisa/rust/lisakmod/src/runtime/fs.rs | 248 ++++++++++++++++++ .../lisa/rust/lisakmod/src/runtime/mod.rs | 1 + 7 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/fs.rs diff --git a/lisa/_assets/kmodules/lisa/pixel6.c b/lisa/_assets/kmodules/lisa/pixel6.c index 2f8934603..a1721de2e 100644 --- a/lisa/_assets/kmodules/lisa/pixel6.c +++ b/lisa/_assets/kmodules/lisa/pixel6.c @@ -39,10 +39,10 @@ typedef struct emeter_buffer_private { unsigned int device; } emeter_buffer_private_t; -static struct file *open_file(int *error, const char *path, umode_t mode) +static struct file *open_file(int *error, const char *path, int flags) { struct file *file; - file = filp_open(path, mode, 0); + file = filp_open(path, flags, 0); if (IS_ERR_OR_NULL(file)) { pr_err("Could not open %s: %li\n", path, file ? PTR_ERR(file) : 0); *error |= 1; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock index 69a2caee6..e59dd9614 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock @@ -26,6 +26,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "getrandom" version = "0.3.2" @@ -78,6 +84,7 @@ name = "lisakmod" version = "0.1.0" dependencies = [ "anyhow", + "embedded-io", "itertools", "linkme", "lisakmod_macros", diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml index 05240ea55..869c113c8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml @@ -8,6 +8,7 @@ crate-type = ["staticlib"] [dependencies] anyhow = { version = "1.0", default-features = false } +embedded-io = {version = "0.6.1", default-features = false, features = ["alloc"]} itertools = {version = "0.14", default-features = false, features = ["use_alloc"]} linkme = "0.3.31" # hashbrown = "0.15" diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs index 76639e39d..38221f485 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs @@ -86,6 +86,13 @@ impl StdError for Error { // } } +impl embedded_io::Error for Error { + #[inline] + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + /// Mirror the anyhow::Context API, but returns the original Result type rather than /// Result pub trait ContextExt { 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 b91a575cc..dc8473781 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -10,6 +10,11 @@ use lisakmod_macros::inlinec::{cconstant, cfunc}; use crate::runtime::printk::pr_err; +pub const PAGE_SIZE: usize = match cconstant!("#include ", "PAGE_SIZE") { + None => 1, + Some(x) => x, +}; + #[inline] fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { let minalign: usize = diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/fs.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/fs.rs new file mode 100644 index 000000000..c39894709 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/fs.rs @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::{ffi::CString, vec::Vec}; +use core::{ + cmp::max, + ffi::{CStr, c_int}, + ptr::NonNull, +}; + +use embedded_io::Read; +use lisakmod_macros::inlinec::{NegativeError, PtrError, cconstant, cfunc, opaque_type}; + +use crate::{ + error::{Error, error}, + runtime::printk::pr_err, +}; + +pub enum OpenFlags { + ReadOnly, + WriteOnly, +} + +impl From for c_int { + fn from(flags: OpenFlags) -> c_int { + match flags { + OpenFlags::ReadOnly => cconstant!("#include ", "O_RDONLY").unwrap(), + OpenFlags::WriteOnly => cconstant!("#include ", "O_WRONLY").unwrap(), + } + } +} + +opaque_type!( + struct CFile, + "struct file", + "linux/fs.h", +); + +pub struct File { + c_file: NonNull, + pos: usize, + path: CString, +} + +impl File { + pub fn open(path: &str, flags: OpenFlags, _mode: u32) -> Result { + // kernel_file_open() would be more appropriate for in-kernel use, as filp_open() opens in + // the context of the current userspace thread. It's somewhat ok since we only open files + // during module init, and this runs as root anyway. + #[cfunc] + fn filp_open(path: &CStr, flags: c_int, mode: u32) -> Result, PtrError> { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_KERNEL_FEATURE(FILE_IO) + return filp_open(path, flags, mode); + #else + return ERR_PTR(-ENOSYS); + #endif + "# + } + let path: CString = CString::new(path) + .map_err(|err| error!("Could not convert path {path} to CString: {err}"))?; + let c_file = filp_open(path.as_c_str(), flags.into(), 0) + .map_err(|err| error!("Could not open file at {path:?}: {err}"))?; + Ok(File { + c_file, + pos: 0, + path, + }) + } + + pub fn read_to_end(&mut self) -> Result, Error> { + let increment = crate::runtime::alloc::PAGE_SIZE; + let mut buf = Vec::new(); + let mut idx = 0; + loop { + let _buf = { + let len = buf.len(); + if idx >= len { + let _increment = max(increment, idx - len); + buf.resize(len + _increment, 0); + } + &mut buf[idx..] + }; + match self.read(_buf) { + // Reached EOF + Ok(0) => { + buf.resize(idx, 0); + return Ok(buf); + } + Ok(read) => { + idx += read; + } + Err(err) => { + return Err(err); + } + } + } + } +} + +unsafe impl Send for File {} + +impl Drop for File { + fn drop(&mut self) { + #[cfunc] + unsafe fn filp_close(file: NonNull) -> Result<(), c_int> { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_KERNEL_FEATURE(FILE_IO) + return filp_close(file, 0); + #else + return -ENOSYS; + #endif + "# + } + unsafe { + filp_close(self.c_file) + .map_err(|err| pr_err!("Could not close file: {err}")) + .expect("Failed to close file"); + } + } +} + +impl embedded_io::ErrorType for File { + type Error = Error; +} + +impl embedded_io::Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + #[cfunc] + unsafe fn kernel_read( + file: NonNull, + buf: *mut u8, + count: usize, + pos: usize, + ) -> Result> { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_KERNEL_FEATURE(FILE_IO) + loff_t off = pos; + return kernel_read(file, buf, count, &off); + #else + return -ENOSYS; + #endif + "# + } + + let count = buf.len(); + if count == 0 { + Err(error!("Tried to read into an empty buffer")) + } else { + let ptr = buf.as_mut_ptr(); + let read = unsafe { kernel_read(self.c_file, ptr, count, self.pos) } + .map_err(|err| error!("Could not read file {:?}: {err}", self.path))?; + self.pos += read; + Ok(read) + } + } +} + +impl embedded_io::Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + #[cfunc] + unsafe fn kernel_write( + file: NonNull, + buf: *const u8, + size: usize, + pos: usize, + ) -> Result> { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_KERNEL_FEATURE(FILE_IO) + loff_t off = pos; + return kernel_write(file, buf, size, &off); + #else + return -ENOSYS; + #endif + "# + } + + let count = buf.len(); + let ptr = buf.as_ptr(); + let read = unsafe { kernel_write(self.c_file, ptr, count, self.pos) } + .map_err(|err| error!("Could not write file {:?}: {err}", self.path))?; + self.pos += read; + Ok(read) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + #[cfunc] + unsafe fn flush(file: NonNull) -> Result<(), c_int> { + r#" + #include + #include "introspection.h" + "#; + + r#" + #if HAS_KERNEL_FEATURE(FILE_IO) + if (file->f_op->flush) { + return file->f_op->flush(file, NULL); + } else { + return 0; + } + #else + return -ENOSYS; + #endif + "# + } + unsafe { flush(self.c_file) } + .map_err(|err| error!("Could not flush file {:?}: {err}", self.path)) + } +} + +impl embedded_io::Seek for File { + #[inline] + fn seek(&mut self, pos: embedded_io::SeekFrom) -> Result { + match pos { + embedded_io::SeekFrom::Start(start) => { + self.pos = start.try_into().unwrap(); + Ok(start) + } + embedded_io::SeekFrom::Current(delta) => { + let cur: i64 = self.pos.try_into().unwrap(); + let new = cur + delta; + self.pos = new.try_into().unwrap(); + Ok(new.try_into().unwrap()) + } + embedded_io::SeekFrom::End(_end) => Err(error!("Seek from end is not supported")), + } + } +} 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 4ce864bf0..8883636e5 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ pub mod alloc; +pub mod fs; pub mod kbox; pub mod module; pub mod panic; -- GitLab From b5b67418178ac818daed9d2222178a63734a965e Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:05:14 +0100 Subject: [PATCH 13/18] lisa._assets.kmodules.lisa: Rework wq bindings FIX * Fix a race condition when dropping WorkItemInner (use disable_delayed_work_sync() rather than cancel_delayed_work_sync() and use manual synchronization with the work when disable_delayed_work_sync() is not available). * Fix the use of TAIT for rustc <= 1.86 --- .../kmodules/lisa/rust/lisakmod/src/lib.rs | 3 + .../lisa/rust/lisakmod/src/runtime/wq.rs | 274 +++++++++++++----- 2 files changed, 200 insertions(+), 77 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index 7c779ff1c..c9cadb15a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -9,6 +9,9 @@ #![feature(type_alias_impl_trait)] #![feature(arbitrary_self_types_pointers)] #![feature(formatting_options)] +#![feature(try_trait_v2)] +#![feature(fn_traits)] +#![feature(unboxed_closures)] extern crate alloc; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs index cfda58ad9..92f7b8817 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/wq.rs @@ -3,18 +3,18 @@ use alloc::boxed::Box; use core::{ cell::UnsafeCell, - convert::{AsMut, AsRef, Infallible}, + convert::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 pin_project::pin_project; use crate::{ mem::{FromContained, impl_from_contained}, - runtime::sync::{Lock as _, LockdepClass, Mutex, PinnedLock}, + runtime::sync::{LockdepClass, Mutex, PinnedLock}, }; incomplete_opaque_type!( @@ -46,7 +46,8 @@ impl Wq { "#; r#" - return alloc_workqueue(name, WQ_FREEZABLE, 0); + // Expose the workqueue in sysfs if a user needs to tune the CPU placement. + return alloc_workqueue(name, WQ_SYSFS | WQ_FREEZABLE, 0); "#; } @@ -82,11 +83,15 @@ opaque_type!( "linux/workqueue.h" ); -#[pin_project(PinnedDrop)] +#[pin_project] pub struct DelayedWork<'wq> { #[pin] c_dwork: CDelayedWork, wq: &'wq Wq, + // Flag set to true when the work should not be re-enqueued anymore. + // This flag is unnecessary if disable_delayed_work_sync() function is available but for older + // kernel < 6.10 where we only have cancel_delayed_work_sync(), we need to handle that manually + disable: bool, } // 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 @@ -99,56 +104,29 @@ impl_from_contained!(('wq) DelayedWork<'wq>, c_dwork: CDelayedWork); impl<'wq> DelayedWork<'wq> { #[inline] - fn enqueue(self: Pin<&mut Self>, delay: u64) { + fn enqueue(self: Pin<&mut Self>, delay_us: u64) { #[cfunc] - fn enqueue( - wq: &UnsafeCell, - dwork: Pin<&mut CDelayedWork>, - delay: u64, - ) -> Result<(), c_int> { + fn enqueue(wq: &UnsafeCell, dwork: Pin<&mut CDelayedWork>, delay_us: u64) -> bool { r#" #include #include "#; r#" - queue_delayed_work(wq, dwork, usecs_to_jiffies(delay)); - return 0; + return queue_delayed_work(wq, dwork, usecs_to_jiffies(delay_us)); "#; } 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); - "# + if !*this.disable { + // If the work was already enqueued, queue_delayed_work() will return false and not do + // anything. queue_delayed_work() can also return false when the work was disabled. + let _ = enqueue(this.wq.c_wq(), this.c_dwork, delay_us); } - 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> { +pub struct WorkItemInner<'wq, F> { // SAFETY: WorkItemInner _must_ be pinned as the address of __dwork will be passed around C // APIs. #[pin] @@ -156,16 +134,34 @@ pub struct WorkItemInner<'wq, F: 'wq + Send + Sync> { pub __f: F, } -impl_from_contained!(('wq, F: Send + Sync) WorkItemInner<'wq, F>, __dwork: DelayedWork<'wq>); +impl_from_contained!(('wq, F) WorkItemInner<'wq, F>, __dwork: DelayedWork<'wq>); impl<'wq, F> WorkItemInner<'wq, F> where - F: Send + Sync, + // SAFETY: We only require F to be Send instead of Send + Sync as the workqueue API guarantees + // that the worker will never execute more than once at a time provided the following + // conditions are met: + // > Workqueue guarantees that a work item cannot be re-entrant if the following conditions hold + // > after a work item gets queued: + // > 1. The work function hasn’t been changed. + // > 2. No one queues the work item to another workqueue. + // > 3. The work item hasn’t been reinitiated. + // > In other words, if the above conditions hold, the work item is guaranteed to be executed by + // > at most one worker system-wide at any given time. + // + // Condition 1. is trivially satisfied as we never update the DelayedWork. + // Condition 2. is trivially satisfied as we never enqueue the DelayedWork to more than one + // workqueue. + // Condition 3. is trivially satisfied as we never re-initialize a work item. + // + // So we can assume that the workqueue API will take care of synchronization and all we need is + // F to be Send. + F: Send, { #[inline] - fn new(wq: &'wq Wq, f: F) -> Pin>> + fn new(wq: &'wq Wq, wrapper: W) -> Pin>> where - F: Worker, + W: ClosureWrapper, { #[cfunc] unsafe fn init_dwork( @@ -186,9 +182,16 @@ where // 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 __dwork = DelayedWork { + c_dwork, + wq, + disable: false, + }; let new = Box::pin(PinnedLock::new(Mutex::new( - WorkItemInner { __dwork, __f: f }, + WorkItemInner { + __dwork, + __f: wrapper.closure(), + }, LockdepClass::new(), ))); @@ -202,7 +205,7 @@ where .__dwork .project() .c_dwork, - F::worker() as *const c_void, + W::worker() as *const c_void, ) } .expect("Could not initialize workqueue's delayed work"); @@ -234,8 +237,15 @@ type PinnedWorkItemInner<'wq, F> = PinnedLock>>; pub struct WorkItem<'wq, F> where - F: Send + Sync, + F: Send, { + // SAFETY: The WorkItemInner need to _always_ be accessed after locking the lock, i.e. even if + // we have an &mut, we cannot use that knowledge to bypass taking the lock. This is because the + // actual worker materializes a shared reference and takes the lock, so we need to play ball + // with it as well. If the design was using some sort of guard type borrowing WorkItemInner + // returned by WorkItem::enqueue(), we would not be able to get an &mut until all guards were + // dropped, but we don't so we need to be careful + // // 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 @@ -243,48 +253,105 @@ where inner: Pin>>, } +impl<'wq, F> Drop for WorkItem<'wq, F> +where + F: Send, +{ + #[inline] + fn drop(&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. + // + // This looks like it belongs to + #include "introspection.h" + "#; + + r#" + #if HAS_SYMBOL(disable_delayed_work_sync) + disable_delayed_work_sync(dwork); + #else + cancel_delayed_work_sync(dwork); + #endif + "# + } + + // We get the pointer, then unlock, then disable the work. This avoids a deadlock where the + // worker would attempt to lock the WorkItemInner after we locked it, and we would wait for + // the worker to finish before unlocking. + let c_dwork = self.with_dwork(|dwork| { + let dwork = dwork.project(); + *dwork.disable = true; + let c_dwork: &CDelayedWork = dwork.c_dwork.as_ref().get_ref(); + c_dwork as *const CDelayedWork as *mut _ + }); + unsafe { + disable_delayed_work_sync(c_dwork); + } + } +} + impl<'wq, F> WorkItem<'wq, F> where - F: Fn(&mut dyn AbstractWorkItem) + Send + Sync + Worker, + F: Send, { #[inline] - pub fn __private_new(wq: &'wq Wq, f: F) -> WorkItem<'wq, F> { + pub fn __private_new(wq: &'wq Wq, wrapper: W) -> WorkItem<'wq, F> + where + W: ClosureWrapper, + { WorkItem { - inner: WorkItemInner::new(wq, f), + inner: WorkItemInner::new(wq, wrapper), } } #[inline] - pub fn enqueue(&self, delay: u64) { - self.inner - .as_ref() - .lock() - .as_mut() - .project() - .__dwork - .enqueue(delay) + fn with_dwork(&self, f: _F) -> T + where + _F: FnOnce(Pin<&mut DelayedWork<'wq>>) -> T, + { + f(self.inner.as_ref().lock().as_mut().project().__dwork) } #[inline] - fn cancel_sync(self: Pin<&mut Self>) { - self.inner - .as_ref() - .lock() - .as_mut() - .project() - .__dwork - .cancel_sync() + pub fn enqueue(&self, delay_us: u64) { + self.with_dwork(|dwork| dwork.enqueue(delay_us)) } } pub trait AbstractWorkItem { - fn enqueue(&mut self, delay: u64); + fn enqueue(&mut self, delay_us: u64); } impl<'wq> AbstractWorkItem for Pin<&mut DelayedWork<'wq>> { #[inline] - fn enqueue(&mut self, delay: u64) { - self.as_mut().enqueue(delay) + fn enqueue(&mut self, delay_us: u64) { + self.as_mut().enqueue(delay_us) } } @@ -295,9 +362,12 @@ incomplete_opaque_type!( ); impl __CWorkStruct { + /// # Safety + /// + /// This function assumes that the __CWorkStruct is nested inside a PinnedWorkItemInner. pub unsafe fn __to_work_item<'wq, F>(self: Pin<&Self>) -> Pin<&PinnedWorkItemInner<'wq, F>> where - F: Send + Sync, + F: Send, { #[cfunc] fn to_dwork(work: Pin<&__CWorkStruct>) -> Pin<&CDelayedWork> { @@ -314,21 +384,71 @@ impl __CWorkStruct { } } +/// # Safety +/// +/// This trait must not be implemented by the user directly. Use the new_work_item!() macro +/// instead. pub unsafe trait Worker { fn worker() -> unsafe extern "C" fn(*mut __CWorkStruct); } +pub trait ClosureWrapper: Worker { + type Closure: FnMut(&mut dyn AbstractWorkItem) + Send; + fn closure(self) -> Self::Closure; +} + 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) + // SAFETY: We need to ensure Send for the closure, as WorkItem relies on that + // to soundly implement Send + pub type Closure = impl ::core::ops::FnMut(&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 { + // A layer is necessary as we cannot implement Worker directly for Closure due to this + // issue: + // https://github.com/rust-lang/rust/issues/139583 + struct Wrapper { + closure: Closure, + } + + struct MyClosure { + closure: Closure, + } + impl ::core::ops::FnOnce<(&mut dyn $crate::runtime::wq::AbstractWorkItem,)> for MyClosure { + type Output = (); + extern "rust-call" fn call_once( + mut self, + args: (&mut dyn $crate::runtime::wq::AbstractWorkItem,), + ) -> Self::Output { + (self.closure)(args.0) + } + } + impl ::core::ops::FnMut<(&mut dyn $crate::runtime::wq::AbstractWorkItem,)> for MyClosure { + extern "rust-call" fn call_mut( + &mut self, + args: (&mut dyn $crate::runtime::wq::AbstractWorkItem,), + ) -> Self::Output { + (self.closure)(args.0) + } + } + + impl $crate::runtime::wq::ClosureWrapper for Wrapper { + type Closure = MyClosure; + #[inline] + // TODO: On Rust 1.86, using the Closure type alias directly triggers an error stating + // the type is unconstrained by appears in closure() return type. As a workaround, we + // encapsulate the closure into MyClosure, which hides the type alias and everything + // works on older rustc versions. + fn closure(self) -> MyClosure { + MyClosure { + closure: self.closure, + } + } + } + + unsafe impl $crate::runtime::wq::Worker for Wrapper { 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) { @@ -350,7 +470,7 @@ macro_rules! new_work_item { } } - $crate::runtime::wq::WorkItem::__private_new($wq, closure) + $crate::runtime::wq::WorkItem::__private_new($wq, Wrapper { closure }) }}; } #[allow(unused_imports)] -- GitLab From e8405f78fdc927bcc15c3735d6922183d2c1e15b Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:08:49 +0100 Subject: [PATCH 14/18] lisa._assets.kmodules.lisa: Rework traceevent bindings FEATURE Allow &str event fields. In order to achieve that, we need to pass a struct rust_str to the C code. Unfortunately, a tracepoint parameter cannot be larger than 64 bits (some generic kernel code requires that). To workaround that problem, pass all tracepoint parameters by reference. --- lisa/_assets/kmodules/lisa/process_rust.py | 37 +++++++++++++++---- .../lisa/rust/lisakmod/src/runtime/mod.rs | 2 +- .../rust/lisakmod/src/runtime/traceevent.rs | 35 ++++++++++++++---- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/process_rust.py b/lisa/_assets/kmodules/lisa/process_rust.py index 3856a8302..952dddb26 100755 --- a/lisa/_assets/kmodules/lisa/process_rust.py +++ b/lisa/_assets/kmodules/lisa/process_rust.py @@ -140,19 +140,24 @@ def main(): if (path := args.out_trace_events_header): class Field: - def __init__(self, name, logical_type, c_arg_type): + def __init__(self, name, logical_type, c_field_type, c_arg_type, c_arg_header): self.name = name self.logical_type = logical_type self.c_arg_type = c_arg_type + self.c_arg_header = c_arg_header + self.c_field_type = c_field_type @property def tp_struct_entry(self): typ = self.logical_type # TODO: support arrays - if typ == 'string': - return f'__string({self.name}, {self.name})' + if typ == 'c-string': + return f'__string({self.name}, *({self.name}))' + elif typ == 'rust-string': + # Add +1 for the null-terminator + return f'__dynamic_array(char, {self.name}, {self.name}->len + 1)' elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): - return f'__field({self.c_arg_type}, {self.name})' + return f'__field({self.c_field_type}, {self.name})' else: raise ValueError(f'Unsupported logical type: {typ}') @@ -164,10 +169,15 @@ def main(): def tp_fast_assign(self): typ = self.logical_type # TODO: support arrays - if typ == 'string': - return f'__lisa_assign_str({self.name}, {self.name});' + if typ == 'c-string': + return f'__lisa_assign_str({self.name}, *({self.name}));' + elif typ == 'rust-string': + return textwrap.dedent(f''' + memcpy(__get_dynamic_array({self.name}), {self.name}->data, {self.name}->len * sizeof(char)); + ((char *)__get_dynamic_array({self.name}))[{self.name}->len] = 0; + ''') elif typ in ('u8', 's8', 'u16', 's16', 'u32', 's32', 'u64', 's64'): - return f'{self.entry} = {self.name};' + return f'{self.entry} = *({self.name});' else: raise ValueError(f'Unsupported logical type: {typ}') @@ -183,7 +193,7 @@ def main(): return (f'{self.name}=%lld', [self.entry]) elif typ == 'u64': return (f'{self.name}=%llu', [self.entry]) - elif typ == 'string': + elif typ in ('rust-string', 'c-string'): return (f'{self.name}=%s', [f'__get_str({self.name})']) else: raise ValueError(f'Unsupported logical type: {typ}') @@ -195,6 +205,8 @@ def main(): name=field['name'], logical_type=field['logical-type'], c_arg_type=field['c-arg-type'], + c_arg_header=field['c-arg-header'], + c_field_type=field['c-field-type'], ) for field in entry['fields'] ] @@ -217,6 +229,7 @@ def main(): field.tp_fast_assign for field in fields ) + printk_fmts, printk_args = zip(*( field.tp_printk for field in fields @@ -226,7 +239,15 @@ def main(): printk_fmt = json.dumps(printk_fmt) printk_args = ', '.join(itertools.chain.from_iterable(printk_args)) + headers = '\n'.join( + f'#include "{header}"' + for field in fields + if (header := field.c_arg_header) + ) + return textwrap.dedent(f''' + {headers} + TRACE_EVENT({name}, TP_PROTO({proto}), TP_ARGS({args}), 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 8883636e5..89e038de8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -12,7 +12,7 @@ pub mod traceevent; pub mod tracepoint; pub mod wq; -// FIXME: remove ? +// TODO: 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/traceevent.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs index 452701b3c..e86aa6471 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/traceevent.rs @@ -2,9 +2,17 @@ use core::ffi::CStr; -use lisakmod_macros::inlinec::IntoFfi; +use lisakmod_macros::inlinec::{FfiType, IntoFfi}; -pub trait FieldTy: IntoFfi { +pub type AsPtrArg = *mut ::FfiType; + +pub trait FieldTy +where + Self: FfiType + IntoFfi, + // Since the tracepoint parameters have to fit in a u64 due to some kernel constraints, we pass + // everything by reference. + AsPtrArg: FfiType + IntoFfi, +{ const NAME: &'static str; } @@ -26,7 +34,8 @@ impl_field!(u32, "u32"); impl_field!(i32, "s32"); impl_field!(u64, "u64"); impl_field!(i64, "s64"); -impl_field!(&CStr, "string"); +impl_field!(&CStr, "c-string"); +impl_field!(&str, "rust-string"); macro_rules! new_event { ($name:ident, fields: {$($field_name:ident: $field_ty:ty),* $(,)?}) => {{ @@ -38,7 +47,17 @@ macro_rules! new_event { { "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) + "c-field-type": (<$field_ty as ::lisakmod_macros::inlinec::FfiType>::C_TYPE), + // All values are passed by pointer rather than being passed directly, as + // tracepoints parameters must not be larger than 64 bits. Since some + // parameter are (e.g. &str), we simply pass everything by reference. + "c-arg-type": (<$crate::runtime::traceevent::AsPtrArg::<$field_ty> as ::lisakmod_macros::inlinec::FfiType>::C_TYPE), + "c-arg-header": ( + match <$crate::runtime::traceevent::AsPtrArg::<$field_ty> as ::lisakmod_macros::inlinec::FfiType>::C_HEADER { + Some(header) => header, + None => "" + } + ) } ),* ] @@ -46,7 +65,7 @@ macro_rules! new_event { { #[::lisakmod_macros::inlinec::cfunc] - fn emit($($field_name: $field_ty),*) { + fn emit($($field_name: $crate::runtime::traceevent::AsPtrArg::<$field_ty>),*) { r#" #include "ftrace_events.h" "#; @@ -68,11 +87,11 @@ macro_rules! new_event { // with a closure. // 2. Make the closure !Copy, so that client code does not accidentally relies on that. struct NonCopy; - let x = NonCopy; + let noncopy = NonCopy; Ok::<_, $crate::error::Error>( move |$($field_name: $field_ty),*| { - let _ = &x; - emit($($field_name),*) + let _ = &noncopy; + emit($(&mut ::lisakmod_macros::inlinec::IntoFfi::into_ffi($field_name)),*) } ) } -- GitLab From e93db7a247a186cf05d67ce9029ca633d459a052 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:11:12 +0100 Subject: [PATCH 15/18] lisa._assets.kmodules.lisa: Fix race condition in Makefile FIX Fix race condition when making an object file copy in extract-binstore function. --- lisa/_assets/kmodules/lisa/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index c2490b68b..ae7e529de 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -79,7 +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_TRACE_EVENTS_H := $(RUST_GENERATED)/trace_events.h RUST_BUILD_DIR := $(RUST_GENERATED)/build RUST_C_SHIMS_DIR := $(RUST_BUILD_DIR)/rust_c_shims @@ -125,8 +125,7 @@ $(RUST_OBJECT_RAW): $(RUST_BUILD_DIR) $(CARGO_TARGET_DIR) 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' + $(OBJDUMP) -h $(1) | awk '{ print $$2 }' | grep '^.binstore.$(2)' | sort | xargs -r -P$$(nproc) -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(3)/_$$0 $(1) /dev/null' endef @@ -146,7 +145,8 @@ $(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) --out-trace-events-header $(RUST_TRACE_EVENTS_HEADER) + 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_H) + cat $(RUST_TRACE_EVENTS_H) # Garbage collect unused sections in the object file, after we have # extracted the binstore sections (otherwise they get discarded). @@ -210,7 +210,7 @@ ifneq ($(KERNELRELEASE),) LISA_KMOD_NAME ?= lisa obj-m := $(LISA_KMOD_NAME).o -$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o pixel6.o introspection_data.o rust/lisakmod/runtime.o rust/lisakmod/tests.o generated/rust/rust.o +$(LISA_KMOD_NAME)-y := main.o tp.o wq.o features.o introspection_data.o rust/lisakmod/runtime.o rust/lisakmod/tests.o generated/rust/rust.o # -fno-stack-protector is needed to avoid possibly undefined __stack_chk_guard symbol ccflags-y := "-I$(MODULE_SRC)" "-I$(MODULE_OBJ)" -std=gnu11 -fno-stack-protector -Wno-declaration-after-statement -Wno-error -- GitLab From 09cb6c60956063ea83e898790d13fc4da3fb0ac9 Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:19:01 +0100 Subject: [PATCH 16/18] lisa._assets.kmodules.lisa: Add parsec module FEATURE Add parser combinator library to support the pixel6 emeter feature. --- .../kmodules/lisa/rust/lisakmod/src/lib.rs | 1 + .../kmodules/lisa/rust/lisakmod/src/parsec.rs | 582 ++++++++++++++++++ 2 files changed, 583 insertions(+) create mode 100644 lisa/_assets/kmodules/lisa/rust/lisakmod/src/parsec.rs diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index c9cadb15a..4aa35e45f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -23,6 +23,7 @@ pub mod init; pub mod lifecycle; pub mod mem; pub mod misc; +pub mod parsec; pub mod prelude; pub mod registry; pub mod runtime; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/parsec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/parsec.rs new file mode 100644 index 000000000..7672e7b99 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/parsec.rs @@ -0,0 +1,582 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::vec::Vec; +use core::{ + convert::Infallible, + fmt, + ops::{ControlFlow, FromResidual, Try}, +}; + +#[derive(Debug, Clone)] +pub enum ParseResult { + Success { remainder: I, x: T }, + Failure { err: E }, + // Hard error, cannot be recovered from. + Error { err: E }, +} + +impl ParseResult { + #[inline] + pub fn into_result(self) -> Result + where + I: fmt::Debug, + T: fmt::Debug, + E: fmt::Display, + { + match self { + ParseResult::Success { x, .. } => Ok(x), + ParseResult::Failure { err } => Err(crate::error::error!("Parse failed: {err}")), + ParseResult::Error { err } => Err(crate::error::error!("Parse errored: {err}")), + } + } + + #[inline] + pub fn unwrap_success(self) -> T + where + I: fmt::Debug, + T: fmt::Debug, + E: fmt::Debug, + { + match self { + ParseResult::Success { x, .. } => x, + _ => panic!("Parse failed: {self:?}"), + } + } + + #[inline] + pub fn from_result(input: I, res: Result) -> ParseResult { + match res { + Ok(x) => ParseResult::Success { + x, + remainder: input, + }, + Err(err) => ParseResult::Failure { err }, + } + } +} + +impl FromResidual for ParseResult { + #[inline] + fn from_residual(residual: ParseResult) -> Self { + match residual { + ParseResult::Failure { err } => ParseResult::Failure { err }, + ParseResult::Error { err } => ParseResult::Error { err }, + } + } +} + +impl Try for ParseResult { + type Output = (I, T); + type Residual = ParseResult; + + fn from_output(output: Self::Output) -> Self { + let (remainder, x) = output; + ParseResult::Success { remainder, x } + } + fn branch(self) -> ControlFlow { + match self { + ParseResult::Success { x, remainder } => ControlFlow::Continue((remainder, x)), + ParseResult::Failure { err } => ControlFlow::Break(ParseResult::Failure { err }), + ParseResult::Error { err } => ControlFlow::Break(ParseResult::Error { err }), + } + } +} + +pub trait Parser +where + I: Input, + E: Error, +{ + fn parse(&mut self, input: I) -> ParseResult; + + #[inline] + fn then(mut self, mut f: F) -> impl Parser + where + Self: Sized, + F: FnMut(T) -> P, + P: Parser, + { + ClosureParser::new(move |input| { + let (remainder, x) = self.parse(input)?; + let mut then = f(x); + then.parse(remainder) + }) + } + + #[inline] + fn or(mut self, mut p: P) -> impl Parser + where + Self: Sized, + P: Parser, + { + ClosureParser::new(move |mut input: I| { + let saved = input.save_pos(); + let res = self.parse(input); + match res { + ParseResult::Success { remainder, x } => ParseResult::Success { remainder, x }, + ParseResult::Failure { .. } => p.parse(saved), + ParseResult::Error { err } => ParseResult::Error { err }, + } + }) + } + + fn map_cut(mut self, mut f: F) -> impl Parser + where + Self: Sized, + F: FnMut(T) -> Result, + { + ClosureParser::new(move |input: I| { + let (remainder, x) = self.parse(input)?; + match f(x) { + Ok(x) => ParseResult::Success { remainder, x }, + Err(err) => ParseResult::Error { err }, + } + }) + } +} + +impl Parser for &mut P +where + I: Input, + E: Error, + P: Parser, +{ + #[inline] + fn parse(&mut self, input: I) -> ParseResult { + (*self).parse(input) + } +} + +pub struct ClosureParser(F); +impl ClosureParser { + #[inline] + pub fn new(f: F) -> ClosureParser { + ClosureParser(f) + } +} + +impl Parser for ClosureParser +where + I: Input, + E: Error, + F: FnMut(I) -> ParseResult, +{ + #[inline] + fn parse(&mut self, input: I) -> ParseResult { + (self.0)(input) + } +} + +pub trait Error: fmt::Debug { + fn from_msg(msg: &'static str) -> Self; +} + +impl Error for &str { + #[inline] + fn from_msg(msg: &'static str) -> Self { + msg + } +} + +impl Error for () { + #[inline] + fn from_msg(_: &'static str) -> Self {} +} + +impl Error for crate::error::Error { + #[inline] + fn from_msg(msg: &'static str) -> Self { + crate::error::error!("{msg}") + } +} + +pub trait Input: fmt::Debug { + type Item; + fn next(&mut self) -> Option; + fn save_pos(&mut self) -> Self; + fn pos(&self) -> usize; + + // FIXME: find a better API that makes it more straightforward to use the "prev" input, rather + // than making it easy to hit the subtle bug where one more item is consumed in the input. + fn to_iter(&mut self) -> impl Iterator + where + Self: Sized, + { + core::iter::from_fn(move || { + let prev = self.save_pos(); + self.next().map(|item| (prev, item)) + }) + } +} + +#[derive(Debug)] +pub struct BytesInput<'a> { + bytes: &'a [u8], + pos: usize, +} + +impl<'a> BytesInput<'a> { + #[inline] + pub fn new(bytes: &'a [u8]) -> BytesInput<'a> { + BytesInput { bytes, pos: 0 } + } +} + +impl<'a> Input for BytesInput<'a> { + type Item = u8; + #[inline] + fn next(&mut self) -> Option { + match self.bytes.get(self.pos) { + Some(b) => { + self.pos += 1; + Some(*b) + } + None => None, + } + } + + #[inline] + fn pos(&self) -> usize { + self.pos + } + + #[inline] + fn save_pos(&mut self) -> Self { + BytesInput { + bytes: self.bytes, + pos: self.pos, + } + } +} + +#[inline] +pub fn any() -> impl Parser +where + I: Input, + E: Error, +{ + ClosureParser::new(move |mut input: I| match input.next() { + None => ParseResult::Failure { + err: E::from_msg("Expected an item but reached end of input"), + }, + Some(item) => ParseResult::Success { + remainder: input, + x: item, + }, + }) +} + +#[inline] +pub fn tag(tag: Tag) -> impl Parser +where + I: Input>, + E: Error, +{ + ClosureParser::new(move |input: I| { + let (remainder, item) = any().parse(input)?; + if item == tag { + ParseResult::Success { remainder, x: item } + } else { + ParseResult::Failure { + err: E::from_msg("Could not recognize expected tag"), + } + } + }) +} + +#[inline] +pub fn not_tag(tag: Tag) -> impl Parser +where + I: Input>, + E: Error, +{ + ClosureParser::new(move |input: I| { + let (remainder, item) = any().parse(input)?; + if item != tag { + ParseResult::Success { remainder, x: item } + } else { + ParseResult::Failure { + err: E::from_msg("Could not recognize expected tag"), + } + } + }) +} + +#[inline] +pub fn tags<'a, Tag, TagI, I, E>(tags: TagI) -> impl Parser +where + Tag: 'a + Clone, + TagI: IntoIterator + Clone, + I: Input>, + E: Error, +{ + ClosureParser::new(move |mut input: I| { + for _tag in tags.clone().into_iter().cloned() { + match tag(_tag).parse(input) { + ParseResult::Success { remainder, .. } => { + input = remainder; + } + ParseResult::Error { err } => return ParseResult::Error { err }, + ParseResult::Failure { err } => return ParseResult::Failure { err }, + } + } + + ParseResult::Success { + remainder: input, + x: (), + } + }) +} + +#[inline] +pub fn many_min_max( + min: Option, + max: Option, + mut parser: P, +) -> impl Parser +where + I: Input, + E: Error, + P: Parser, + Out: FromIterator, +{ + ClosureParser::new(move |mut input: I| { + let mut len = 0; + let mut saved = input.save_pos(); + let mut input = Some(input); + let iter = core::iter::from_fn(|| { + let mut _input: I = input.take().unwrap(); + saved = _input.save_pos(); + match parser.parse(_input) { + ParseResult::Success { remainder, x } => { + input = Some(remainder); + len += 1; + Some(x) + } + _ => None, + } + }); + let out = iter.collect(); + if let Some(min) = min { + if len < min { + return ParseResult::Failure { + err: E::from_msg("Not enough items consumed"), + }; + } + } + if let Some(max) = max { + if len > max { + return ParseResult::Failure { + err: E::from_msg("Too many items consumed"), + }; + } + } + ParseResult::Success { + remainder: saved, + x: out, + } + }) +} + +#[inline] +pub fn many(parser: P) -> impl Parser +where + I: Input, + E: Error, + P: Parser, + Out: FromIterator, +{ + many_min_max(None, None, parser) +} + +#[inline] +pub fn recognize(mut parser: P) -> impl Parser, E> +where + I: Input, + E: Error, + P: Parser, +{ + ClosureParser::new(move |mut input: I| { + let saved = input.save_pos(); + let (remainder, _) = parser.parse(input)?; + + let len = remainder.pos() - saved.pos(); + let mut out = Vec::with_capacity(len); + let mut input = saved; + for _ in 0..len { + let (_input, item) = any().parse(input)?; + input = _input; + out.push(item); + } + ParseResult::Success { + remainder: input, + x: out, + } + }) +} + +#[inline] +pub fn cut(mut parser: P) -> impl Parser +where + I: Input, + E: Error, + P: Parser, +{ + ClosureParser::new(move |input: I| match parser.parse(input) { + res @ (ParseResult::Success { .. } | ParseResult::Error { .. }) => res, + ParseResult::Failure { err } => ParseResult::Error { err }, + }) +} + +#[inline] +pub fn or(parsers: Iter) -> impl Parser +where + I: Input, + E: Error, + P: Parser, + for<'a> &'a Iter: IntoIterator, +{ + ClosureParser::new(move |mut input: I| { + for p in &parsers { + match p.parse(input.save_pos()) { + ParseResult::Success { remainder, x } => { + return ParseResult::Success { remainder, x }; + } + ParseResult::Error { err } => return ParseResult::Error { err }, + _ => {} + } + } + + ParseResult::Failure { + err: E::from_msg("No alternative matched"), + } + }) +} + +// #[inline] +// pub fn and<'a, I, T, E, P, Iter>(parsers: Iter) -> impl Parser +// where +// I: Input, +// E: Error, +// P: 'a + Parser, +// Iter: IntoIterator + Clone, +// { +// ClosureParser::new(move |mut input: I| { +// for p in parsers.clone() { +// match p.parse(input) { +// ParseResult::Success { remainder, .. } => { +// input = remainder; +// } +// ParseResult::Error { err } => return ParseResult::Error { err }, +// ParseResult::Failure { err } => return ParseResult::Failure { err }, +// } +// } + +// ParseResult::Success { +// remainder: input, +// x: (), +// } +// }) +// } + +#[inline] +pub fn u64_decimal() -> impl Parser +where + I: Input>, + E: Error, +{ + // Since decimal number writing system works in "big endian" (most significant digit first), we + // assign to it the highest possible power of ten we can represent with the output type, and at + // the end we divide the result to scale it back to the real value. + let pow_max = u64::MAX.ilog(10); + let mag_max: u64 = 10u64.pow(pow_max); + + ClosureParser::new(move |mut input: I| { + let mut found = false; + let mut acc: u64 = 0; + let mut mag = mag_max; + for (prev, item) in input.to_iter() { + let item: char = item.into(); + let item = match item { + '0' => 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + _ => { + if found { + return ParseResult::Success { + remainder: prev, + x: acc / mag, + }; + } else { + return ParseResult::Failure { + err: E::from_msg("Expected a decimal number"), + }; + } + } + }; + found = true; + mag /= 10; + acc += item * mag; + } + if found { + ParseResult::Success { + remainder: input, + x: acc / mag, + } + } else { + ParseResult::Failure { + err: E::from_msg("Expected a digit but reached end of input"), + } + } + }) +} + +#[inline] +pub fn whitespace() -> impl Parser +where + I: Input>, + E: Error, +{ + ClosureParser::new(move |mut input: I| { + for (prev, item) in input.to_iter() { + let item: char = item.into(); + match item { + ' ' | '\n' | '\t' => {} + _ => { + return ParseResult::Success { + remainder: prev, + x: (), + }; + } + } + } + ParseResult::Success { + remainder: input, + x: (), + } + }) +} + +#[inline] +pub fn eof() -> impl Parser +where + I: Input, + E: Error, +{ + ClosureParser::new(move |mut input: I| match input.next() { + None => ParseResult::Success { + remainder: input, + x: (), + }, + Some(_) => ParseResult::Failure { + err: E::from_msg("Expected end of input but found more input"), + }, + }) +} -- GitLab From 1cb340a3e9514c8bed4224a95eeedd1a1f7ebd1c Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:21:42 +0100 Subject: [PATCH 17/18] lisa._assets.kmodules.lisa: Misc module update * Get the list of feature to enable from module parameter in Rust code. * Simplify the API of define_feature!() by always requiring a FeaturesConfig for parent features. * Rename ContextExt into ResultExt and add some method. * Ensure the features visibility is appropriate. --- .../kmodules/lisa/rust/lisakmod/src/error.rs | 16 +- .../rust/lisakmod/src/features/events/mod.rs | 6 +- .../lisa/rust/lisakmod/src/features/legacy.rs | 149 +++++++--- .../lisa/rust/lisakmod/src/features/mod.rs | 64 +--- .../lisa/rust/lisakmod/src/features/tests.rs | 15 +- .../rust/lisakmod/src/features/tracepoint.rs | 16 +- .../lisa/rust/lisakmod/src/features/wq.rs | 16 +- .../kmodules/lisa/rust/lisakmod/src/init.rs | 281 ++++++++++-------- 8 files changed, 310 insertions(+), 253 deletions(-) diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs index 38221f485..ab64edfb0 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/error.rs @@ -5,6 +5,8 @@ use core::{error::Error as StdError, fmt}; use anyhow; +use crate::runtime::printk::pr_err; + /// Cloneable error type suitable for memoization #[derive(Clone)] pub struct Error { @@ -95,7 +97,7 @@ impl embedded_io::Error for Error { /// Mirror the anyhow::Context API, but returns the original Result type rather than /// Result -pub trait ContextExt { +pub trait ResultExt { fn context(self, context: C) -> Self where C: fmt::Display + Send + Sync + 'static; @@ -104,9 +106,11 @@ pub trait ContextExt { where C: fmt::Display + Send + Sync + 'static, F: FnOnce() -> C; + + fn print_err(self); } -impl ContextExt for Result { +impl ResultExt for Result { #[inline] fn context(self, context: C) -> Self where @@ -123,6 +127,14 @@ impl ContextExt for Result { { Ok(>::with_context(self, f)?) } + + #[inline] + fn print_err(self) { + match self { + Ok(_) => {} + Err(err) => pr_err!("{err:#}"), + } + } } pub struct MultiResult { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/events/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/events/mod.rs index dc7699301..08d95ee68 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/events/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/events/mod.rs @@ -1,19 +1,17 @@ /* SPDX-License-Identifier: GPL-2.0 */ macro_rules! define_event_feature { - (struct $type:ident) => { + (struct $type:ident, $event_name:literal) => { $crate::features::define_feature! { struct $type, - name: concat!("event__", stringify!($type)), + name: concat!("event__", $event_name), visibility: Public, Service: (), Config: (), dependencies: [], init: |_| { Ok($crate::lifecycle::new_lifecycle!(|services| { - $crate::runtime::printk::pr_info!("Enabling ftrace event: {}", stringify!($type)); yield_!(Ok(::alloc::sync::Arc::new(()))); - $crate::runtime::printk::pr_info!("Disabling ftrace event: {}", stringify!($type)); Ok(()) })) }, 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 3e4e1f4c5..5e537bf7a 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/legacy.rs @@ -1,64 +1,125 @@ /* SPDX-License-Identifier: GPL-2.0 */ use alloc::{sync::Arc, vec::Vec}; -use core::ffi::c_int; +use core::{ + ffi::{CStr, c_char, c_int, c_uint, c_void}, + ptr::NonNull, +}; use lisakmod_macros::inlinec::cfunc; use crate::{ - error::error, features::define_feature, lifecycle::new_lifecycle, runtime::printk::pr_err, + error::error, + features::{FeaturesConfig, define_feature}, + lifecycle::new_lifecycle, + runtime::printk::pr_err, }; +#[cfunc] +fn features_array() -> Option> { + r#" + #include + #include "features.h" + + static char *features[MAX_FEATURES]; + static unsigned int features_len = 0; + module_param_array(features, charp, &features_len, 0); + MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Available features are printed when loading the module"); + "#; + + r#" + return features; + "# +} + +#[cfunc] +fn features_array_len() -> c_uint { + r#" + static unsigned int features_len; + "#; + + r#" + return features_len; + "# +} + +pub fn module_param_features() -> Option> { + let len = match features_array_len() { + 0 => None, + x => Some(x), + }?; + + let ptr = features_array()?; + let ptr = ptr.as_ptr() as *const *const c_char; + + let slice = unsafe { core::slice::from_raw_parts(ptr, len as usize) }; + Some(slice.iter().map(|s| { + unsafe { CStr::from_ptr(*s) } + .to_str() + .expect("Invalid UTF-8 in feature name") + })) +} + define_feature! { struct LegacyFeatures, name: "__legacy_features", - visibility: Public, + visibility: Private, Service: (), Config: (), dependencies: [], init: |configs| { - Ok(new_lifecycle!(|services| { - #[cfunc] - fn start() -> Result<(), c_int> { - r#" - #include - #include "features.h" - #include "introspection.h" - - static char *features[MAX_FEATURES]; - static unsigned int features_len = 0; - module_param_array(features, charp, &features_len, 0); - MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Available features are printed when loading the module"); - "#; - - r#" - pr_info("Kernel features detected. This will impact the module features that are available:\n"); - const char *kernel_feature_names[] = {__KERNEL_FEATURE_NAMES}; - const bool kernel_feature_values[] = {__KERNEL_FEATURE_VALUES}; - for (size_t i=0; i < ARRAY_SIZE(kernel_feature_names); i++) { - pr_info(" %s: %s\n", kernel_feature_names[i], kernel_feature_values[i] ? "enabled" : "disabled"); + Ok(( + FeaturesConfig::new(), + new_lifecycle!(|services| { + #[cfunc] + fn list_kernel_features() { + r#" + #include + #include + #include "introspection.h" + "#; + + r#" + pr_info("Kernel features detected. This will impact the module features that are available:\n"); + const char *kernel_feature_names[] = {__KERNEL_FEATURE_NAMES}; + const bool kernel_feature_values[] = {__KERNEL_FEATURE_VALUES}; + for (size_t i=0; i < ARRAY_SIZE(kernel_feature_names); i++) { + pr_info(" %s: %s\n", kernel_feature_names[i], kernel_feature_values[i] ? "enabled" : "disabled"); + } + "# + } + + #[cfunc] + fn start(features: Option>, len: c_uint) -> Result<(), c_int> { + r#" + #include "features.h" + "#; + + r#" + return init_features(len ? features : NULL , len); + "# } - return init_features(features_len ? features : NULL , features_len); - "# - } - - #[cfunc] - fn stop() -> Result<(), c_int> { - r#" - #include "features.h" - "#; - - r#" - return deinit_features(); - "# - } - - // We must not bail out here, otherwise we will never run stop(), which will leave - // tracepoint probes installed after modexit, leading to a kernel crash - if let Err(code) = start() { pr_err!("Failed to start legacy C features: {code}") } - yield_!(Ok(Arc::new(()))); - stop().map_err(|code| error!("Failed to stop legacy C features: {code}")) - })) + + #[cfunc] + fn stop() -> Result<(), c_int> { + r#" + #include "features.h" + "#; + + r#" + return deinit_features(); + "# + } + + list_kernel_features(); + + // We must not bail out here, otherwise we will never run stop(), which will leave + // tracepoint probes installed after modexit, leading to a kernel crash + if let Err(code) = start(features_array(), features_array_len()) { pr_err!("Failed to start legacy C features: {code}") } + yield_!(Ok(Arc::new(()))); + stop().map_err(|code| error!("Failed to stop legacy C features: {code}")) + }) + )) }, } 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 b3e2732ef..0f28f38e2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/mod.rs @@ -113,7 +113,7 @@ mod private { use super::*; use crate::{ - error::{ContextExt, Error, error}, + error::{Error, ResultExt as _, error}, runtime::sync::{LockdepClass, Mutex}, }; @@ -462,64 +462,6 @@ where }) } -pub struct InitBundle { - pub configs: FeaturesConfig, - pub lifecycle: LifeCycleAlias, -} - -impl From> for InitBundle -where - Feat: Feature, -{ - fn from(lifecycle: LifeCycleAlias) -> InitBundle { - InitBundle { - configs: FeaturesConfig::new(), - lifecycle, - } - } -} - -impl From for InitBundle -where - Service: 'static + Send + Sync + From<()>, - Feat: Feature, -{ - fn from(configs: FeaturesConfig) -> InitBundle { - InitBundle { - configs, - lifecycle: new_lifecycle!(|_| { - yield_!(Ok(Arc::new(().into()))); - Ok(()) - }), - } - } -} - -impl From<(FeaturesConfig, LifeCycleAlias)> for InitBundle -where - Feat: Feature, -{ - fn from((configs, lifecycle): (FeaturesConfig, LifeCycleAlias)) -> InitBundle { - InitBundle { configs, lifecycle } - } -} - -impl From<()> for InitBundle -where - Service: 'static + Send + Sync + From<()>, - Feat: Feature, -{ - fn from(_: ()) -> InitBundle { - InitBundle { - configs: FeaturesConfig::new(), - lifecycle: new_lifecycle!(|_| { - yield_!(Ok(Arc::new(().into()))); - Ok(()) - }), - } - } -} - macro_rules! define_feature { ( $vis:vis struct $type:ident, @@ -569,9 +511,7 @@ macro_rules! define_feature { $crate::error::Error, > { - let init: ::core::result::Result<_, $crate::error::Error> = $init(configs); - let init: $crate::features::InitBundle = init?.into(); - Ok((init.configs, init.lifecycle)) + $init(configs) } } }; 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 42e139771..9f00bead8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tests.rs @@ -7,7 +7,7 @@ use lisakmod_macros::inlinec::{cconstant, ceval, cfunc}; use crate::{ error::Error, - features::define_feature, + features::{FeaturesConfig, define_feature}, lifecycle::new_lifecycle, runtime::{ kbox::KernelKBox, @@ -299,10 +299,13 @@ define_feature! { Config: (), dependencies: [], init: |configs| { - Ok(new_lifecycle!(|_| { - init_tests()?; - yield_!(Ok(Arc::new(()))); - Ok(()) - })) + Ok(( + FeaturesConfig::new(), + new_lifecycle!(|_| { + init_tests()?; + yield_!(Ok(Arc::new(()))); + Ok(()) + }) + )) }, } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tracepoint.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tracepoint.rs index 51cb86e0b..2ed02bacc 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tracepoint.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/tracepoint.rs @@ -3,7 +3,10 @@ use alloc::{sync::Arc, vec::Vec}; pub use crate::runtime::tracepoint::*; -use crate::{features::define_feature, lifecycle::new_lifecycle}; +use crate::{ + features::{FeaturesConfig, define_feature}, + lifecycle::new_lifecycle, +}; #[derive(Debug)] pub struct TracepointService { @@ -30,9 +33,12 @@ define_feature! { Config: (), dependencies: [], init: |configs| { - Ok(new_lifecycle!(|services| { - yield_!(Ok(Arc::new(TracepointService::new()))); - Ok(()) - })) + Ok(( + FeaturesConfig::new(), + new_lifecycle!(|services| { + yield_!(Ok(Arc::new(TracepointService::new()))); + Ok(()) + }) + )) }, } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs index 2c05a51ae..5b50f3a9d 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/wq.rs @@ -3,7 +3,10 @@ use alloc::{sync::Arc, vec::Vec}; pub use crate::runtime::wq::*; -use crate::{features::define_feature, lifecycle::new_lifecycle}; +use crate::{ + features::{FeaturesConfig, define_feature}, + lifecycle::new_lifecycle, +}; #[derive(Debug)] pub struct WqService { @@ -28,9 +31,12 @@ define_feature! { Config: (), dependencies: [], init: |configs| { - Ok(new_lifecycle!(|services| { - yield_!(Ok(Arc::new(WqService::new()))); - Ok(()) - })) + Ok(( + FeaturesConfig::new(), + new_lifecycle!(|services| { + yield_!(Ok(Arc::new(WqService::new()))); + Ok(()) + }) + )) }, } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs index 46437b1e2..45b34ba6f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ +use alloc::collections::BTreeSet; use core::ffi::c_int; use crate::{ @@ -8,7 +9,17 @@ use crate::{ }; // FIXME: clean that up - +// use alloc::{collections::BTreeSet, sync::Arc, vec::Vec}; +// use core::ffi::{c_int, c_ulong, c_void}; +// +// use crate::{ +// features::{ +// Feature, FeaturesConfig, FeaturesService, Visibility, define_feature, features_lifecycle, +// tracepoint::TracepointFeature, wq::WqFeature, +// }, +// lifecycle::{LifeCycle, new_lifecycle}, +// runtime::printk::pr_info, +// }; // define_feature! { // struct Feature1, // name: "feature1", @@ -17,126 +28,130 @@ use crate::{ // Config: (), // dependencies: [Feature2, TracepointFeature, WqFeature], // init: |configs| { -// Ok(new_lifecycle!(|services| { -// let services: FeaturesService = services; -// let dropper = services.get::() -// .expect("Could not get service for TracepointFeature") -// .probe_dropper(); -// pr_info!("FEATURE1 start"); -// -// use crate::runtime::tracepoint::Tracepoint; -// use core::sync::atomic::{AtomicUsize, Ordering}; -// -// // let x = AtomicUsize::new(0); -// // let probe = new_probe!( -// // &dropper, -// // (_preempt: bool, _prev: *const c_void, _next:* const c_void, _prev_state: c_ulong) { -// // let x = x.fetch_add(1, Ordering::SeqCst); -// // crate::runtime::printk::pr_info!("SCHED_SWITCH {x}"); -// // } -// // ); -// -// let tp = unsafe { -// Tracepoint::<(bool, *const c_void, * const c_void, c_ulong)>::lookup("sched_switch").expect("tp not found") -// }; -// -// // let registered = tp.register_probe(&probe); -// // drop(probe); -// // drop(dropper); -// // drop(registered); -// // -// -// use crate::runtime::wq; -// let wq = services.get::() -// .expect("Could not get service for WqFeature") -// .wq(); -// -// let work_item_n = AtomicUsize::new(1); -// let work_item = wq::new_work_item!(wq, move |work| { -// pr_info!("FROM WORKER"); -// let n = work_item_n.fetch_add(1, Ordering::SeqCst); -// if n < 10 { -// work.enqueue(10); -// } -// }); -// work_item.enqueue(0); -// msleep(100); -// // work_item.enqueue(0); -// -// -// use core::ffi::CStr; -// let f = crate::runtime::traceevent::new_event! { -// lisa__myevent2, -// fields: { -// field1: u8, -// field3: &CStr, -// field2: u64, -// } -// }?; -// -// use lisakmod_macros::inlinec::cfunc; -// #[cfunc] -// fn msleep(ms: u64) { -// r#" -// #include -// "#; -// -// r#" -// msleep(ms); -// "# -// } -// -// fn run(cmd: &CStr) { +// Ok(( +// FeaturesConfig::new(), +// new_lifecycle!(|services| { +// let services: FeaturesService = services; +// let dropper = services.get::() +// .expect("Could not get service for TracepointFeature") +// .probe_dropper(); +// pr_info!("FEATURE1 start"); +// +// use crate::runtime::tracepoint::Tracepoint; +// use core::sync::atomic::{AtomicUsize, Ordering}; +// +// // let x = AtomicUsize::new(0); +// // let probe = new_probe!( +// // &dropper, +// // (_preempt: bool, _prev: *const c_void, _next:* const c_void, _prev_state: c_ulong) { +// // let x = x.fetch_add(1, Ordering::SeqCst); +// // crate::runtime::printk::pr_info!("SCHED_SWITCH {x}"); +// // } +// // ); +// +// let tp = unsafe { +// Tracepoint::<(bool, *const c_void, * const c_void, c_ulong)>::lookup("sched_switch").expect("tp not found") +// }; +// +// // let registered = tp.register_probe(&probe); +// // drop(probe); +// // drop(dropper); +// // drop(registered); +// // +// +// use crate::runtime::wq; +// let wq = services.get::() +// .expect("Could not get service for WqFeature") +// .wq(); +// +// let work_item_n = AtomicUsize::new(1); +// let work_item = wq::new_work_item!(wq, move |work| { +// pr_info!("FROM WORKER"); +// let n = work_item_n.fetch_add(1, Ordering::SeqCst); +// if n < 10 { +// work.enqueue(10); +// } +// }); +// work_item.enqueue(0); +// msleep(100); +// // work_item.enqueue(0); +// +// +// use core::ffi::CStr; +// let f = crate::runtime::traceevent::new_event! { +// lisa__myevent2, +// fields: { +// field1: u8, +// field3: &CStr, +// field4: &str, +// field2: u64, +// } +// }?; +// +// use lisakmod_macros::inlinec::cfunc; // #[cfunc] -// fn run(cmd: &CStr) -> Result<(), c_int> { +// fn msleep(ms: u64) { // r#" -// #include +// #include // "#; // // r#" -// char *envp[] = { -// "HOME=/", -// "PWD=/", -// "USER=root", -// "PATH=/:/sbin:/bin:/usr/sbin:/usr/bin", -// "SHELL=/bin/sh", -// NULL -// }; -// -// char *argv[] = { -// "/bin/sh", "-c", (char *)cmd, NULL, -// }; -// return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +// msleep(ms); // "# // } -// let _ = run(cmd).map_err(|ret| pr_info!("Command {cmd:?} failed: {ret}")); -// } -// -// pr_info!("TRACING START"); -// run(c"/trace-cmd reset -at > stdout.reset 2>&1"); -// // run(c"/trace-cmd start -Bmybuffer -e all > stdout.start 2>&1"); -// run(c"/trace-cmd start -e all > stdout.start 2>&1"); -// pr_info!("TRACING STARTED"); -// run(c"echo mymsg > /dev/kmsg"); -// -// f(1, c"hello", 42); -// f(2, c"world", 43); -// -// pr_info!("TRACING STOP"); -// run(c"/trace-cmd stop -a > stdout.stop 2>&1"); -// run(c"/trace-cmd extract -at > stdout.extract 2>&1"); -// pr_info!("TRACING STOPED"); -// -// let feature2_service = services.get::(); -// pr_info!("FEATURE2 service: {feature2_service:?}"); -// yield_!(Ok(Arc::new(()))); -// pr_info!("FEATURE1 stop"); -// -// drop(f); -// drop(work_item); -// // drop(registered); -// Ok(()) -// })) +// +// fn run(cmd: &CStr) { +// #[cfunc] +// fn run(cmd: &CStr) -> Result<(), c_int> { +// r#" +// #include +// "#; +// +// r#" +// char *envp[] = { +// "HOME=/", +// "PWD=/", +// "USER=root", +// "PATH=/:/sbin:/bin:/usr/sbin:/usr/bin", +// "SHELL=/bin/sh", +// NULL +// }; +// +// char *argv[] = { +// "/bin/sh", "-c", (char *)cmd, NULL, +// }; +// return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +// "# +// } +// let _ = run(cmd).map_err(|ret| pr_info!("Command {cmd:?} failed: {ret}")); +// } +// +// pr_info!("TRACING START"); +// run(c"/trace-cmd reset -at > stdout.reset 2>&1"); +// // run(c"/trace-cmd start -Bmybuffer -e all > stdout.start 2>&1"); +// run(c"/trace-cmd start -e all > stdout.start 2>&1"); +// pr_info!("TRACING STARTED"); +// run(c"echo mymsg > /dev/kmsg"); +// +// f(1, c"hello", "world", 42); +// f(2, c"hello2", "world2", 43); +// +// pr_info!("TRACING STOP"); +// run(c"/trace-cmd stop -a > stdout.stop 2>&1"); +// run(c"/trace-cmd extract -at > stdout.extract 2>&1"); +// pr_info!("TRACING STOPED"); +// +// let feature2_service = services.get::(); +// pr_info!("FEATURE2 service: {feature2_service:?}"); +// yield_!(Ok(Arc::new(()))); +// pr_info!("FEATURE1 stop"); +// +// drop(f); +// drop(work_item); +// // drop(registered); +// Ok(()) +// }) +// )) // }, // } // @@ -153,21 +168,37 @@ use crate::{ // Config: (), // dependencies: [], // init: |configs| { -// Ok(new_lifecycle!(|services| { -// pr_info!("FEATURE2 start"); -// let service = Feature2Service { a: 42 }; -// yield_!(Ok(Arc::new(service))); -// pr_info!("FEATURE2 stop"); -// Ok(()) -// })) +// Ok(( +// FeaturesConfig::new(), +// new_lifecycle!(|services| { +// pr_info!("FEATURE2 start"); +// let service = Feature2Service { a: 42 }; +// yield_!(Ok(Arc::new(service))); +// pr_info!("FEATURE2 stop"); +// Ok(()) +// }) +// )) // }, // } - // define_event_feature!(struct myevent); pub fn module_main() -> LifeCycle<(), (), c_int> { - let select = |feature: &dyn Feature| feature.visibility() == Visibility::Public; - // let select = |feature: &dyn Feature| feature.name() == "feature2"; + // Always enable the C legacy features, as it is just a proxy to enable the C features. + let select_legacy = |feature: &dyn Feature| feature.name() == "__legacy_features"; + let select_public = |feature: &dyn Feature| { + select_legacy(feature) || feature.visibility() == Visibility::Public + }; - features_lifecycle(select) + let to_enable = crate::features::legacy::module_param_features(); + match to_enable { + None => features_lifecycle(select_public), + Some(features) => { + let features: BTreeSet<_> = features.collect(); + let select = |feature: &dyn Feature| { + select_legacy(feature) + || (select_public(feature) && features.contains(feature.name())) + }; + features_lifecycle(select) + } + } } -- GitLab From 60977b365d7a2565083dc583db50ed52e1af170b Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Fri, 11 Apr 2025 15:12:36 +0100 Subject: [PATCH 18/18] lisa._assets.kmodules.lisa: Move pixel6 feature to Rust FEATURE Move the feature fully from C to Rust. --- lisa/_assets/kmodules/lisa/ftrace_events.h | 27 - lisa/_assets/kmodules/lisa/parsec.h | 684 ------------------ lisa/_assets/kmodules/lisa/pixel6.c | 272 ------- .../lisa/rust/lisakmod/src/features/pixel6.rs | 342 ++++++++- .../kmodules/lisa/rust/lisakmod/src/lib.rs | 4 + 5 files changed, 336 insertions(+), 993 deletions(-) delete mode 100644 lisa/_assets/kmodules/lisa/parsec.h delete mode 100644 lisa/_assets/kmodules/lisa/pixel6.c diff --git a/lisa/_assets/kmodules/lisa/ftrace_events.h b/lisa/_assets/kmodules/lisa/ftrace_events.h index 198d5ba83..9b9aa1f61 100644 --- a/lisa/_assets/kmodules/lisa/ftrace_events.h +++ b/lisa/_assets/kmodules/lisa/ftrace_events.h @@ -474,33 +474,6 @@ TRACE_EVENT(lisa__sched_cpu_capacity, ); #endif - -#define PIXEL6_EMETER_CHAN_NAME_MAX_SIZE 64 - -TRACE_EVENT(lisa__pixel6_emeter, - TP_PROTO(unsigned long ts, unsigned int device, unsigned int chan, const char *chan_name, unsigned long value), - TP_ARGS(ts, device, chan, chan_name, value), - - TP_STRUCT__entry( - __field(unsigned long, ts ) - __field(unsigned long, value ) - __field(unsigned int, device ) - __field(unsigned int, chan ) - __array(char, chan_name, PIXEL6_EMETER_CHAN_NAME_MAX_SIZE ) - ), - - TP_fast_assign( - __entry->ts = ts; - __entry->device = device; - __entry->chan = chan; - __entry->value = value; - strscpy(__entry->chan_name, chan_name, PIXEL6_EMETER_CHAN_NAME_MAX_SIZE); - ), - - TP_printk("ts=%lu device=%u chan=%u chan_name=%s value=%lu", - __entry->ts, __entry->device, __entry->chan, __entry->chan_name, __entry->value) -); - #endif /* _FTRACE_EVENTS_H */ /* This part must be outside protection */ diff --git a/lisa/_assets/kmodules/lisa/parsec.h b/lisa/_assets/kmodules/lisa/parsec.h deleted file mode 100644 index 26ff4ea9d..000000000 --- a/lisa/_assets/kmodules/lisa/parsec.h +++ /dev/null @@ -1,684 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _PARSEC_H -#define _PARSEC_H - -#include -#include -#include - -/** - * struct parse_buffer - Input buffer to parsers defined in that header. - * @data: Pointer to the data. - * @size: Size of the data buffer. - * @capacity: Maximum size of the data pointed. This can be larger than @size - * when describing a window into the buffer. - * @private: Pointer to additional data to be passed down the parser chain. - */ -typedef struct parse_buffer { - u8 *data; - size_t size; - size_t capacity; - void *private; -} parse_buffer; - -/** - * WITH_NULL_TERMINATED() - Call a function taking a const char * as first - * parameter. - * @buff: Buffer to be turned into a null-terminated string. - * @f: Function to call. - * @__VA_ARGS__: Extra parameters passed to the function. - */ -#define WITH_NULL_TERMINATED(buff, f, ...) \ - ({ \ - u8 *data; \ - u8 *end; \ - u8 last; \ - bool allocate = (buff)->size + 1 > (buff)->capacity; \ - if (unlikely(allocate)) { \ - data = kmalloc((buff)->size + 1, GFP_KERNEL); \ - BUG_ON(!data); \ - end = data + (buff)->size; \ - memcpy(data, (buff)->data, (buff)->size); \ - } else { \ - data = (buff)->data; \ - end = data + (buff)->size; \ - last = *end; \ - } \ - *end = '\0'; \ - typeof(f(NULL)) res = f((const char *)data, ##__VA_ARGS__); \ - if (unlikely(allocate)) \ - kfree(data); \ - else \ - *end = last; \ - res; \ - }) - -/** - * charp2parse_buffer() - Convert a null-terminated string to struct parse_buffer. - * @s: Null terminated string. - * @private: Pointer to additional data to be passed down the parser chain. - */ -static inline parse_buffer charp2parse_buffer(char *s, void *private) -{ - size_t size = strlen(s) + 1; - return (parse_buffer){ .data = (u8 *)s, - .private = private, - .size = size, - .capacity = size }; -} - -/** - * parse_buffer2charp() - Copy a struct parse_buffer into a null-terminated string. - * @src: Buffer to copy from. - * @dst: Null terminated string to copy into. - * @max: Maximum number of bytes to copy. - * - * The @dst string is guaranteed to be null-terminated after calling this function. - */ -static inline size_t parse_buffer2charp(parse_buffer *src, char *dst, - size_t max) -{ - // Ensure we always provide a null-terminated string, even if the - // buffer is empty. - if (max) - dst[0] = '\0'; - - size_t to_copy = min(max, src->size); - size_t to_zero = min(to_copy, max - 1); - if (to_copy) { - memcpy(dst, src->data, to_copy); - dst[to_zero] = '\0'; - } - return to_copy; -} - -/** - * parse_buffer_strdup() - Copy the parse_buffer into a newly allocated string. - * @src: Buffer to copy from. - * - * The string string is guaranteed to be null-terminated. - */ -static inline char *parse_buffer_strdup(parse_buffer *src) { - size_t size = src->size + 1; - char *s = NULL; - if (size) { - s = kmalloc(size, GFP_KERNEL); - if (s) { - parse_buffer2charp(src, s, size); - } - } - return s; -} - -/** - * enum parse_result_tag - Tag indicating the result of a parser. - * @PARSE_SUCCESS: The parse was successful and the value parsed is therefore - * meaningful. - * @PARSE_FAILURE: The parse failed and the value should not be inspected. - */ -enum parse_result_tag { - PARSE_SUCCESS, - PARSE_FAILURE, -}; - -/** - * PARSE_RESULT() - Wrap a type name to turn it into the result type of a parser. - * @type: Type to wrap. This must be a single identifier, e.g. unsigned char - * needs to be referred to as unchar instead. - * - * Note: Before being used with PARSE_RESULT(type), a wrapper type needs to be - * defined using DEFINE_PARSE_RESULT_TYPE(type). - */ -#define PARSE_RESULT(type) parse_result_##type - -/** - * DEFINE_PARSE_RESULT_TYPE() - Define a wrapper so that @type can be used in PARSE_RESULT(@type). - * @type: Type to wrap. This must be a single identifier, e.g. unsigned char - * needs to be referred to as unchar instead. - */ -#define DEFINE_PARSE_RESULT_TYPE(type) \ - typedef struct PARSE_RESULT(type) { \ - parse_buffer remainder; \ - enum parse_result_tag tag; \ - type value; \ - } PARSE_RESULT(type) - -/* Define only the types used in this header. Users are responsible for any - * other type - */ -DEFINE_PARSE_RESULT_TYPE(parse_buffer); -DEFINE_PARSE_RESULT_TYPE(u8); -DEFINE_PARSE_RESULT_TYPE(u64); -DEFINE_PARSE_RESULT_TYPE(int); -DEFINE_PARSE_RESULT_TYPE(ulong); -typedef char *str; -DEFINE_PARSE_RESULT_TYPE(str); - -/** - * IS_SUCCESS() - Evaluates to true if the parse result is successful. - * @res: Value of type PARSE_RESULT(type) - */ -#define IS_SUCCESS(res) (res.tag == PARSE_SUCCESS) - -/** - * parse_string() - Recognize the given string. - * @input: parse_buffer input to the parser. - * @match: String to match @input against. - */ -static inline PARSE_RESULT(parse_buffer) - parse_string(parse_buffer *input, const char *match) -{ - size_t len = strlen(match); - if (input->size < len) { - return (PARSE_RESULT(parse_buffer)){ .tag = PARSE_FAILURE, - .remainder = *input }; - } else { - if (!memcmp(input->data, match, len)) { - return (PARSE_RESULT( - parse_buffer)){ .tag = PARSE_SUCCESS, - .remainder = - (parse_buffer){ - .private = input->private, - .data = input->data + - len, - .size = input->size - - len, - .capacity = - input->capacity - - len, - }, - .value = { - .private = input->private, - .data = input->data, - .size = len, - .capacity = - input->capacity, - } }; - } else { - return (PARSE_RESULT(parse_buffer)){ - .tag = PARSE_FAILURE, - .remainder = *input, - }; - } - } -} - -static inline PARSE_RESULT(u8) - __parse_u8_in(parse_buffer *input, const bool lookup[256], bool revert) -{ - - if (input->size) { - u8 input_char = *input->data; - bool success = lookup[(size_t)input_char]; - success = revert ? !success : success; - success = input_char == '\0' ? false : success; - - if (success) { - return (PARSE_RESULT(u8)){ - .tag = PARSE_SUCCESS, - .remainder = - (parse_buffer){ - .private = input->private, - .data = input->data + 1, - .size = input->size - 1, - .capacity = - input->capacity - - 1 }, - .value = input_char, - }; - } - } - return (PARSE_RESULT(u8)){ .tag = PARSE_FAILURE, .remainder = *input }; -} - - -// Build the lookup table in a way that allows the compiler to optimize-out the -// code so the table is initialized only once, given that the lookup table is -// actually stored in a static variable. Clang is very good at optimizing this -// out from experiments. -static inline __attribute__((always_inline)) void __init_char_lookup(const char *allowed, bool lookup[256]) { - // Using strlen makes the pattern obvious to the compiler that - // is then able to optimize-out the loop. - size_t allowed_len = strlen(allowed); - for (size_t i=0; i < allowed_len; i++) { - lookup[(size_t)allowed[i]] = true; - } -} - - -#define __CHAR_IN(name, allowed, revert) \ - static inline PARSE_RESULT(u8) name(parse_buffer *input) \ - { \ - static bool lookup[256] = {0}; \ - __init_char_lookup((allowed), lookup); \ - return __parse_u8_in(input, lookup, (revert)); \ - } - - -/** - * CHAR_IN() - Recognize one character in the set passed in @allowed. - * @allowed: Null-terminated string containing the characters to recognize. - */ -#define CHAR_IN(name, allowed) __CHAR_IN(name, allowed, false) - -/** - * CHAR_NOT_IN() - Recognize one character not in the set passed in @allowed. - * @allowed: Null-terminated string containing the characters to not recognize. - */ -#define CHAR_NOT_IN(name, allowed) __CHAR_IN(name, allowed, true) - - -static inline PARSE_RESULT(u8) - __parse_u8(parse_buffer *input, u8 c, bool revert) -{ - if (input->size && (revert ? *input->data != c : *input->data == c)) { - return (PARSE_RESULT(u8)){ - .tag = PARSE_SUCCESS, - .remainder = - (parse_buffer){ .private = input->private, - .data = input->data + 1, - .size = input->size - 1, - .capacity = - input->capacity - 1 }, - .value = c, - }; - } else { - return (PARSE_RESULT(u8)){ .tag = PARSE_FAILURE, - .remainder = *input }; - } -} - -/** - * parse_char() - Recognize one character equal to the one given. - * @input: parse_buffer input to the parser. - * @c: Character to recognize. - */ -static inline PARSE_RESULT(u8) parse_char(parse_buffer *input, u8 c) -{ - return __parse_u8(input, c, false); -} - -/** - * parse_not_char() - Recognize one character not equal to the one given. - * @input: parse_buffer input to the parser. - * @c: Character to not recognize. - */ -static inline PARSE_RESULT(u8) parse_not_char(parse_buffer *input, u8 c) -{ - return __parse_u8(input, c, true); -} - -/** - * APPLY() - Combinator to apply a function to some arguments. - * @type: Return type of the parsers. - * @name: Name of the new parser to create. - * @parser: Function of type: (parse_buffer *input, ...) -> PARSE_RESULT(type) - * @__VA_ARGS__: Parameters to pass to the @parser after the parse_buffer input. - */ -#define APPLY(type, name, parser, ...) \ - static inline PARSE_RESULT(type) name(parse_buffer *input) \ - { \ - return parser(input, __VA_ARGS__); \ - } - -/** - * OR() - Combinator that tries @parser1 then @parser2 if @parser1 failed. - * @type: Return type of the parsers. - * @name: Name of the new parser to create. - * @parser1: First parser to try. - * @parser2: Second parser to try. - */ -#define OR(type, name, parser1, parser2) \ - static inline PARSE_RESULT(type) name(parse_buffer *input) \ - { \ - PARSE_RESULT(type) res1 = parser1(input); \ - if (IS_SUCCESS(res1)) { \ - return res1; \ - } else { \ - return parser2(input); \ - } \ - } - -/** - * PURE() - Create a parser that does not consume any input and returns @value. - * @type: Return type of the parsers. - * @name: Name of the new parser to create. - * @value: Value to return. - * - * Note: This parser can be used e.g. to terminate a chain of OR() with a parser - * that cannot fail and just provide a default value. - */ -#define PURE(type, name, _value) \ - static inline PARSE_RESULT(type) name(parse_buffer *input) \ - { \ - return (PARSE_RESULT(type)){ .tag = PARSE_SUCCESS, \ - .remainder = *input, \ - .value = (_value) }; \ - } - -/** - * MAP() - Combinator that maps a function over the returned value of @parser - * if it succeeded. - * @f_type: Return type of the function. - * @parser_type: Return type of the parser. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - * @f: Function converting the parser's output. - */ -#define MAP(f_type, parser_type, name, parser, f) \ - static inline PARSE_RESULT(f_type) name(parse_buffer *input) \ - { \ - PARSE_RESULT(parser_type) res = parser(input); \ - if (IS_SUCCESS(res)) { \ - return (PARSE_RESULT( \ - f_type)){ .tag = PARSE_SUCCESS, \ - .remainder = res.remainder, \ - .value = f(res.value) }; \ - } else { \ - return (PARSE_RESULT( \ - f_type)){ .tag = PARSE_FAILURE, \ - .remainder = res.remainder }; \ - } \ - } - -/** - * MAP_PARSE_BUFFER() - Similar to MAP() but specialized to struct parse_buffer. - * - * The function will be called with a null-terminated string provided by - * WITH_NULL_TERMINATED(). - */ -#define MAP_PARSE_BUFFER(f_type, parser_type, name, parser, f) \ - static inline typeof(f(NULL)) \ - __map_parse_buffer_##f(parse_buffer buff) \ - { \ - return WITH_NULL_TERMINATED(&buff, f); \ - } \ - MAP(f_type, parser_type, name, parser, __map_parse_buffer_##f) - - -static inline char *__strdup(const char *src) { - if (src) { - size_t size = strlen(src) + 1; - char *dst = kmalloc(size, GFP_KERNEL); - if (dst) - memcpy(dst, src, size); - return dst; - } else { - return NULL; - } -} - -/** - * STRDUP() - Transforms a parser returning a parse_buffer to a parser - * returning an char * allocated with kmalloc. - */ -#define STRDUP(name, parser) MAP_PARSE_BUFFER(str, parse_buffer, name, parser, __strdup) - -/** - * PEEK() - Combinator that applies the parser but does not consume any input. - * @type: Return type of the parser. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - * @__VA_ARGS__: Extra arguments to pass to the parser. - */ -#define PEEK(type, name, parser, ...) \ - static inline PARSE_RESULT(type) name(parse_buffer *input) \ - { \ - PARSE_RESULT(type) res = parser(input, ##__VA_ARGS__); \ - res.remainder = *input; \ - return res; \ - } - -/** - * AT_LEAST() - Combinator that applies the parser at least @n times. If less - * than @n times match, rewind the input. - * @type: Return type of @f. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - * @f: Function folded over the parser's output. Each time a parse is - * successful, it will be called with 1. the accumulator's value and 2. the - * value of the parse. The return value is fed back into the accumulator. - * @__VA_ARGS__: Extra arguments to pass to the parser. - * - * Note: The resulting parser takes a 2nd parameter of type @type, which is the - * initial value of the accumulator maintained by @f. - */ -#define AT_LEAST(type, name, parser, f, n, ...) \ - static inline PARSE_RESULT(type) name(parse_buffer *input, type init) \ - { \ - typeof(init) acc = init; \ - typeof(parser(NULL)) res = \ - (typeof(res)){ .remainder = *input }; \ - for (size_t i = 0;; i++) { \ - res = parser(&res.remainder, ##__VA_ARGS__); \ - if (IS_SUCCESS(res)) { \ - acc = f(acc, res.value); \ - } else { \ - return (PARSE_RESULT(type)){ \ - .tag = i >= n ? PARSE_SUCCESS : \ - PARSE_FAILURE, \ - .remainder = res.remainder, \ - .value = acc \ - }; \ - } \ - } \ - } - -/** - * MANY() - Same as AT_LEAST() with n=0 - */ -#define MANY(type, name, parser, f, ...) AT_LEAST(type, name, parser, f, 0, ##__VA_ARGS__) - -/** - * TAKEWHILE_AT_LEAST() - Combinator that applies @parser at least @n times and - * returns a parse_buffer spanning the recognized input. - * @type: Return type of @parser. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - * @n: Minimum number of times @parser need to be successful in order to succeed. - */ -#define TAKEWHILE_AT_LEAST(type, name, parser, n) \ - static inline PARSE_RESULT(parse_buffer) name(parse_buffer *input) \ - { \ - PARSE_RESULT(type) \ - res = (PARSE_RESULT(type)){ .remainder = *input }; \ - for (size_t i = 0;; i++) { \ - res = parser(&res.remainder); \ - if (!IS_SUCCESS(res)) { \ - if (i >= n) \ - return (PARSE_RESULT(parse_buffer)){ \ - .tag = PARSE_SUCCESS, \ - .value = \ - (parse_buffer){ \ - .private = input->private, \ - .data = input->data, \ - .size = res.remainder \ - .data - \ - input->data, \ - .capacity = \ - input->capacity }, \ - .remainder = res.remainder, \ - }; \ - else \ - return (PARSE_RESULT(parse_buffer)){ \ - .tag = PARSE_FAILURE, \ - .remainder = *input, \ - }; \ - } \ - } \ - } -/** - * TAKEWHILE() - Same as TAKEWHILE_AT_LEAST() with n=0 - */ -#define TAKEWHILE(type, name, parser) TAKEWHILE_AT_LEAST(type, name, parser, 0) - -/** - * COUNT_MANY() - Combinator that applies @parser until it fails and returns how many times it succeeded. - * @parser_type: Return type of @parser. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - */ -#define COUNT_MANY(parser_type, name, parser) \ - static inline int __count_fold_##name(int acc, int x) \ - { \ - return acc + x; \ - } \ - static inline int __count_one_f_##name(parser_type _) \ - { \ - return 1; \ - } \ - MAP(int, parser_type, __count_one_##name, parser, \ - __count_one_f_##name); \ - MANY(int, __many_##name, __count_one_##name, __count_fold_##name) \ - static inline PARSE_RESULT(int) name(parse_buffer *input) \ - { \ - return __many_##name(input, 0); \ - } - -/** - * THEN() - Combinator that applies @parser1, applies @parser2 with the return - * value of @parser1 and returns the result of @parser2. - * @parser1_type: Return type of @parser1. - * @parser2_type: Return type of @parser2. - * @name: Name of the new parser to create. - * @parser1: First parser to apply. - * @parser2: Second parser to apply and return the value of. It must take a 2nd - * parameter that is the return type of @parser1. - */ -#define THEN(parser1_type, parser2_type, name, parser1, parser2) \ - static inline PARSE_RESULT(parser2_type) name(parse_buffer *input) \ - { \ - PARSE_RESULT(parser1_type) res = parser1(input); \ - PARSE_RESULT(parser2_type) res2; \ - if (IS_SUCCESS(res)) { \ - res2 = parser2(&res.remainder, res.value); \ - if (IS_SUCCESS(res2)) \ - return res2; \ - } \ - return (PARSE_RESULT(parser2_type)){ .tag = PARSE_FAILURE, \ - .remainder = *input }; \ - } - -/** - * RIGHT() - Same as THEN() except @parser1 output is simply discarded, and is not passed to @parser2. - */ -#define RIGHT(parser1_type, parser2_type, name, parser1, parser2) \ - PARSE_RESULT(parser2_type) \ - static inline __discard_then_##parser2(parse_buffer *input, \ - parser1_type _) \ - { \ - return parser2(input); \ - } \ - THEN(parser1_type, parser2_type, name, parser1, \ - __discard_then_##parser2) - -/** - * LEFT() - Same as THEN() except @parser2 output is simply discarded, - * and @parser1 result is returned instead. - */ -#define LEFT(parser1_type, parser2_type, name, parser1, parser2) \ - PARSE_RESULT(parser1_type) \ - static inline __forward_then_discard_##parser2(parse_buffer *input, \ - parser1_type value) \ - { \ - PARSE_RESULT(parser2_type) res = parser2(input); \ - return (PARSE_RESULT(parser1_type)){ .tag = PARSE_SUCCESS, \ - .value = value, \ - .remainder = \ - res.remainder }; \ - } \ - THEN(parser1_type, parser1_type, name, parser1, \ - __forward_then_discard_##parser2) - -/** - * PARSE() - Apply a parser in the body of SEQUENCE(). - * @parser: Parser to apply - * @__VA_ARGS__: Arguments to pass to the parser. - */ -#define PARSE(parser, ...) \ - ({ \ - typeof(parser(&__seq_remainder, ##__VA_ARGS__)) res = \ - parser(&__seq_remainder, ##__VA_ARGS__); \ - __seq_remainder = res.remainder; \ - if (!IS_SUCCESS(res)) \ - goto __seq_failure; \ - res.value; \ - }) - -/** - * SEQUENCE() - Build a custom sequence of parsers in a more friendly way than - * THEN(). - * @type: Return type of the sequence. - * @name: Name of the new parser to create. - * @body: Statement expr containing the custom logic. The last statement will be - * the value returned by the parser. Private data from parse_buffer can be - * accessed inside the body through the `private` variable. - */ -#define SEQUENCE(type, name, body, ...) \ - static inline PARSE_RESULT(type) \ - name(parse_buffer *input, ##__VA_ARGS__) \ - { \ - parse_buffer __seq_remainder = *input; \ - parse_buffer __seq_unmodified_input = *input; \ - void *private = input->private; \ - (void)private; \ - type __seq_value = (body); \ - return (PARSE_RESULT(type)){ \ - .tag = PARSE_SUCCESS, \ - .remainder = __seq_remainder, \ - .value = __seq_value, \ - }; \ - __seq_failure: \ - return (PARSE_RESULT(type)){ \ - .tag = PARSE_FAILURE, \ - .remainder = __seq_unmodified_input, \ - }; \ - } - -/** - * DISCARD() - Discard the output of the given parser - * @parser_type: Return type of @parser. - * @name: Name of the new parser to create. - * @parser: Parser to wrap. - */ -#define DISCARD(parser_type, name, parser) RIGHT(parser_type, void_t, name, parser, parse_void) - -/* - * Collection of common basic parsers. - */ -typedef struct { - u8 __internal; -} void_t; -DEFINE_PARSE_RESULT_TYPE(void_t); -PURE(void_t, parse_void, (void_t){0}); - -CHAR_IN(parse_whitespace, " \n\t"); -COUNT_MANY(u8, __count_whitespaces, parse_whitespace); -DISCARD(int, consume_whitespaces, __count_whitespaces); - -static inline unsigned long __to_ulong(const char *s) -{ - unsigned long res; - if (kstrtoul(s, 10, &res)) - return 0; - else - return res; -} - -static inline unsigned long __to_u64(const char *s) -{ - u64 res; - if (kstrtou64(s, 10, &res)) - return 0; - else - return res; -} - -CHAR_IN(parse_digit, "0123456789"); -TAKEWHILE_AT_LEAST(u8, parse_number_string, parse_digit, 1); -MAP_PARSE_BUFFER(ulong, parse_buffer, parse_ulong, parse_number_string, - __to_ulong); -MAP_PARSE_BUFFER(u64, parse_buffer, parse_u64, parse_number_string, - __to_u64); -#endif diff --git a/lisa/_assets/kmodules/lisa/pixel6.c b/lisa/_assets/kmodules/lisa/pixel6.c deleted file mode 100644 index a1721de2e..000000000 --- a/lisa/_assets/kmodules/lisa/pixel6.c +++ /dev/null @@ -1,272 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#include -#include -#include -#include - -#include "main.h" -#include "features.h" -#include "wq.h" -#include "ftrace_events.h" -#include "parsec.h" -#include "introspection.h" - -/* There is no point in setting this value to less than 8 times what is written - * in usec to POWER_METER_RATE_FILE - */ -#define POWER_METER_SAMPLING_RATE_MS 50 - -#define POWER_METER_SAMPLE_FILE_MAX_SIZE 1024 -#define POWER_METER_SAMPLE_FILE_0 "/sys/bus/iio/devices/iio:device0/energy_value" -#define POWER_METER_RATE_FILE_0 "/sys/bus/iio/devices/iio:device0/sampling_rate" -#define POWER_METER_SAMPLE_FILE_1 "/sys/bus/iio/devices/iio:device1/energy_value" -#define POWER_METER_RATE_FILE_1 "/sys/bus/iio/devices/iio:device1/sampling_rate" - -#if HAS_KERNEL_FEATURE(FILE_IO) - -static PARSE_RESULT(int) parse_content(parse_buffer *); - -typedef struct sample { - unsigned long ts; - unsigned long value; - unsigned int device; - unsigned int chan; - char chan_name[PIXEL6_EMETER_CHAN_NAME_MAX_SIZE]; -} sample_t; -DEFINE_PARSE_RESULT_TYPE(sample_t); - -typedef struct emeter_buffer_private { - unsigned int device; -} emeter_buffer_private_t; - -static struct file *open_file(int *error, const char *path, int flags) -{ - struct file *file; - file = filp_open(path, flags, 0); - if (IS_ERR_OR_NULL(file)) { - pr_err("Could not open %s: %li\n", path, file ? PTR_ERR(file) : 0); - *error |= 1; - file = NULL; - } - return file; -} - -static void close_file(int *error, struct file *file) -{ - if (file) { - int close_ret = filp_close(file, 0); - if (close_ret) { - pr_err("Could not close file: %i\n", close_ret); - *error |= 1; - } - } -} - -static void write_str(int *error, struct file *file, char *str) -{ - ssize_t ret = -ENOENT; - - if (file) - ret = kernel_write(file, str, strlen(str) + 1, 0); - - if (ret < 0) { - pr_err("Could not write string: %zi\n", ret); - *error |= 1; - } -} - -struct p6_emeter_data { - struct work_item *work; - struct file *sample_file_0; - struct file *sample_file_1; -}; - -static int free_p6_emeter_data(struct p6_emeter_data *data) { - int ret = 0; - if (data) { - ret |= destroy_work(data->work); - - close_file(&ret, data->sample_file_0); - close_file(&ret, data->sample_file_1); - - kfree(data); - } - return ret; -} - -static void process_content(unsigned char *content, size_t content_capacity, unsigned int device) -{ - - size_t size = strlen(content) + 1; - emeter_buffer_private_t private = { - .device = device, - }; - parse_buffer input = { - .data = (u8 *)content, - .size = size, - .capacity = content_capacity, - .private = &private, - }; - - PARSE_RESULT(int) res = parse_content(&input); - if (!IS_SUCCESS(res)) - pr_err("Failed to parse content\n"); -} - -static void read_and_process(char *buffer, unsigned int size, struct file *file, char *name, - unsigned int device) { - ssize_t count = 0; - - count = kernel_read(file, buffer, POWER_METER_SAMPLE_FILE_MAX_SIZE - 1, 0); - if (count < 0 || count >= POWER_METER_SAMPLE_FILE_MAX_SIZE) { - pr_err("Could not read %s : %ld\n", name, count); - } else { - buffer[count] = '\0'; - process_content(buffer, size, device); - } -} - -static int p6_emeter_worker(void *data) { - struct feature *feature = data; - struct p6_emeter_data *p6_emeter_data = feature_data(feature); - char content[POWER_METER_SAMPLE_FILE_MAX_SIZE]; - - read_and_process(content, ARRAY_SIZE(content), p6_emeter_data->sample_file_0, POWER_METER_SAMPLE_FILE_0, 0); - read_and_process(content, ARRAY_SIZE(content), p6_emeter_data->sample_file_1, POWER_METER_SAMPLE_FILE_1, 1); - - /* Schedule the next run using the same delay as previously */ - return WORKER_SAME_DELAY; -} - - -static int enable_p6_emeter(struct feature *feature) { - struct p6_emeter_data *data = NULL; - struct file *sample_file = NULL; - struct file *rate_file = NULL; - int ret = 0; - -#define HANDLE_ERR(code) if (code) {ret |= code; goto finish;} - - HANDLE_ERR(ENABLE_FEATURE(__workqueue)) - - data = kzalloc(sizeof(*data), GFP_KERNEL); - if (!data) - HANDLE_ERR(1); - feature_data_set(feature, data); - - /* Note that this is the hardware sampling rate. Software will only see - *an updated value every 8 hardware periods - */ - rate_file = open_file(&ret, POWER_METER_RATE_FILE_0, O_WRONLY); - write_str(&ret, rate_file, "500\n"); - close_file(&ret, rate_file); - HANDLE_ERR(ret); - - rate_file = open_file(&ret, POWER_METER_RATE_FILE_1, O_WRONLY); - write_str(&ret, rate_file, "500\n"); - close_file(&ret, rate_file); - HANDLE_ERR(ret); - - sample_file = open_file(&ret, POWER_METER_SAMPLE_FILE_0, O_RDONLY); - data->sample_file_0 = sample_file; - HANDLE_ERR(ret); - - sample_file = open_file(&ret, POWER_METER_SAMPLE_FILE_1, O_RDONLY); - data->sample_file_1 = sample_file; - HANDLE_ERR(ret); - - data->work = start_work(p6_emeter_worker, msecs_to_jiffies(POWER_METER_SAMPLING_RATE_MS), feature); - if (!data->work) - ret |= 1; -finish: - return ret; -#undef HANDLE_ERR -}; - -static int disable_p6_emeter(struct feature* feature) { - int ret = 0; - struct p6_emeter_data *data = feature_data(feature); - - if (data) - free_p6_emeter_data(data); - - ret |= DISABLE_FEATURE(__workqueue); - - return ret; -}; -/*********************************************** - * Parser for the energy_value sysfs file format - ***********************************************/ - -APPLY(u8, parse_name_char, parse_not_char, ']'); -TAKEWHILE(u8, parse_name, parse_name_char); - -SEQUENCE(sample_t, parse_sample, ({ - sample_t value; - - value.device = ((emeter_buffer_private_t*)private)->device; - - /* CH42 */ - PARSE(parse_string, "CH"); - value.chan = PARSE(parse_ulong); - - /* (T=42) */ - PARSE(parse_string, "(T="); - value.ts = PARSE(parse_ulong); - PARSE(parse_char, ')'); - - /* [CHAN_NAME] */ - PARSE(parse_char, '['); - parse_buffer _name = PARSE(parse_name); - parse_buffer2charp(&_name, value.chan_name, - PIXEL6_EMETER_CHAN_NAME_MAX_SIZE); - PARSE(parse_char, ']'); - - /* , */ - PARSE(parse_string, ", "); - - /* 12345 */ - value.value = PARSE(parse_ulong); - - value; -})) - -LEFT(sample_t, void_t, parse_sample_line, parse_sample, consume_whitespaces); - -static int process_sample(int nr, sample_t sample) -{ - /* pr_info("parsed: device=%u chan=%u, ts=%lu chan_name=%s value=%lu\n", sample.device, sample.chan, */ - /* sample.ts, sample.chan_name, sample.value); */ - trace_lisa__pixel6_emeter(sample.ts, sample.device, sample.chan, sample.chan_name, sample.value); - return nr + 1; -} - -MANY(int, parse_all_samples, parse_sample_line, process_sample); - -/* This parser is able to parse strings formatted like that: - "t=473848\nCH42(T=473848)[S10M_VDD_TPU], 3161249\nCH1(T=473848)[VSYS_PWR_MODEM], 48480309\nCH2(T=473848)[VSYS_PWR_RFFE], 9594393\nCH3(T=473848)[S2M_VDD_CPUCL2], 28071872\nCH4(T=473848)[S3M_VDD_CPUCL1], 17477139\nCH5(T=473848)[S4M_VDD_CPUCL0], 113447446\nCH6(T=473848)[S5M_VDD_INT], 12543588\nCH7(T=473848)[S1M_VDD_MIF], 25901660\n" -*/ -SEQUENCE(int, parse_content, ({ - /* t=12345 */ - PARSE(parse_string, "t="); - PARSE(parse_number_string); - PARSE(consume_whitespaces); - - /* Parse all the following sample lines */ - PARSE(parse_all_samples, 0); -})) - -#else -#warning "event__lisa__pixel6_emeter feature will not be available due to kernel not exporting necessary symbols" -static int enable_p6_emeter(struct feature* feature) { - pr_err("The kernel does not export the required symbols for that feature"); - return 1; -} - -static int disable_p6_emeter(struct feature* feature) { - return 0; -} -#endif - - -DEFINE_FEATURE(event__lisa__pixel6_emeter, enable_p6_emeter, disable_p6_emeter); diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/pixel6.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/pixel6.rs index d7578bd71..4d990f9f5 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/pixel6.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/features/pixel6.rs @@ -4,14 +4,336 @@ // features::{Feature, Visibility}, // lifecycle::new_lifecycle, // }; +// +use alloc::{ + format, + string::{String, ToString as _}, + sync::Arc, + vec, + vec::Vec, +}; -// fn make_feature() -> Feature { -// Feature::new( -// "pixel6".into(), -// Visibility::Public, -// new_lifecycle!({ -// yield Ok(()); -// Ok(()) -// }), -// ) -// } +use embedded_io::{Seek as _, Write as _}; + +use crate::{ + error::{Error, ResultExt as _, error}, + features::{FeaturesConfig, FeaturesService, define_feature, wq::WqFeature}, + lifecycle::new_lifecycle, + parsec::{self, ClosureParser, ParseResult, Parser}, + runtime::{ + fs::{File, OpenFlags}, + traceevent::new_event, + wq, + }, +}; + +type DeviceId = u32; +type ChanId = u32; + +#[derive(Debug, PartialEq)] +struct Sample { + ts: u64, + value: u64, + chan: ChanId, + chan_name: String, +} + +fn emeter_parser(f: &mut F) -> impl Parser +where + I: parsec::Input, + F: FnMut(Sample) -> Result<(), Error>, +{ + use crate::parsec::{eof, many, tags, u64_decimal, whitespace}; + ClosureParser::new(move |input: I| { + let (input, _) = tags(b"t=").parse(input)?; + let (input, _ts) = u64_decimal().parse(input)?; + let (input, _) = whitespace().parse(input)?; + let (input, res): (_, Result<(), Error>) = many(emeter_row_parser(f)).parse(input)?; + let (input, res) = ParseResult::from_result(input, res)?; + let (input, _) = whitespace().parse(input)?; + let (input, _) = eof().parse(input)?; + ParseResult::Success { + remainder: input, + x: res, + } + }) +} + +fn emeter_row_parser(f: &mut F) -> impl Parser, Error> +where + I: parsec::Input, + F: FnMut(Sample) -> Result<(), Error>, +{ + use crate::parsec::{many, not_tag, tag, tags, u64_decimal, whitespace}; + ClosureParser::new(move |input: I| { + let (input, _) = tags(b"CH").parse(input)?; + let (input, chan) = u64_decimal() + .map_cut(|x| { + x.try_into() + .map_err(|err| error!("Could not convert u64 to ChanId: {err}")) + }) + .parse(input)?; + let (input, _) = tags(b"(T=").parse(input)?; + let (input, ts) = u64_decimal().parse(input)?; + let (input, _) = tag(b')').parse(input)?; + + let (input, _) = tag(b'[').parse(input)?; + + let (input, chan_name) = many(not_tag(b']')) + .map_cut(|s| { + String::from_utf8(s) + .map_err(|err| error!("Could not convert channel name to UTF-8 string: {err}")) + }) + .parse(input)?; + + let (input, _) = tag(b']').parse(input)?; + let (input, _) = tag(b',').parse(input)?; + let (input, _) = whitespace().parse(input)?; + let (input, value) = u64_decimal().parse(input)?; + + let (input, _) = whitespace().parse(input)?; + + let sample = Sample { + ts, + value, + chan, + chan_name, + }; + + ParseResult::Success { + remainder: input, + x: f(sample), + } + }) +} + +struct Device { + value_file: File, + rate_file: File, + config: DeviceConfig, +} + +impl Device { + fn parse_samples(&mut self, f: &mut F) -> Result<(), Error> + where + F: FnMut(Sample) -> Result<(), Error>, + { + self.value_file.rewind()?; + let samples = self.value_file.read_to_end()?; + let input = parsec::BytesInput::new(&samples); + let mut parser = emeter_parser(f); + parser.parse(input).into_result() + } +} + +struct DeviceConfig { + id: DeviceId, + folder: String, + hardware_sampling_rate_us: u64, +} + +struct Pixel6EmeterConfig { + devices: Vec, +} + +// /* There is no point in setting this value to less than 8 times what is written +// * in usec to POWER_METER_RATE_FILE +// */ +// const POWER_METER_SAMPLING_RATE_MS: u64 = 50; + +define_feature! { + struct Pixel6Emeter, + name: "lisa__pixel6_emeter", + visibility: Public, + Service: (), + Config: (), + dependencies: [WqFeature], + init: |configs| { + Ok(( + FeaturesConfig::new(), + new_lifecycle!(|services| { + let services: FeaturesService = services; + let wq = services.get::() + .expect("Could not get service for WqFeature") + .wq(); + + let hardware_sampling_rate_us = 500; + let config = Pixel6EmeterConfig { + devices: vec![ + DeviceConfig { + id: 0, + folder: "/sys/bus/iio/devices/iio:device0/".into(), + hardware_sampling_rate_us, + }, + DeviceConfig { + id: 1, + folder: "/sys/bus/iio/devices/iio:device1/".into(), + hardware_sampling_rate_us, + }, + ] + }; + + let mut devices = config.devices.into_iter().map(|device_config| { + let value_file = File::open( + &(device_config.folder.to_string() + "/energy_value"), + OpenFlags::ReadOnly, + 0, + )?; + let mut rate_file = File::open( + &(device_config.folder.to_string() + "/sampling_rate"), + OpenFlags::WriteOnly, + 0, + )?; + + // Note that this is the hardware sampling rate. Software will only see an + // updated value every 8 hardware periods + writeln!(rate_file, "{}", device_config.hardware_sampling_rate_us).map_err(|err| error!("Could not write to sampling_rate file: {err}"))?; + + let device = Device { + value_file, + rate_file, + config: device_config, + }; + Ok(device) + }).collect::, Error>>()?; + + let emit = new_event! { + lisa__pixel6_emeter, + fields: { + ts: u64, + device: DeviceId, + chan: ChanId, + chan_name: &str, + value: u64, + } + }?; + + // There is no point in setting this value to less than 8 times what is written in + // usec to the sampling_rate file, as the hardware will only expose a new value + // every 8 hardware periods. + let software_sampling_rate_us = hardware_sampling_rate_us / 8; + + let work_item = wq::new_work_item!(wq, move |work| { + let process_device = |device: &mut Device| -> Result<(), Error> { + let device_id = device.config.id; + device.parse_samples(&mut |sample: Sample| { + emit(sample.ts, device_id, sample.chan, &sample.chan_name, sample.value); + Ok(()) + })?; + Ok(()) + }; + for device in &mut devices { + process_device(device) + .with_context(|| format!("Could not read samples from device {}", device.config.id)) + .print_err(); + } + work.enqueue(software_sampling_rate_us); + }); + work_item.enqueue(software_sampling_rate_us); + yield_!(Ok(Arc::new(()))); + Ok(()) + }), + )) + }, +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::*; + use crate::parsec; + + #[test] + fn test_emeter_parser() { + let input = b"t=473848\nCH42(T=473848)[S10M_VDD_TPU], 3161249\nCH1(T=473848)[VSYS_PWR_MODEM], 48480309\nCH2(T=473848)[VSYS_PWR_RFFE], 9594393\nCH3(T=473848)[S2M_VDD_CPUCL2], 28071872\nCH4(T=473848)[S3M_VDD_CPUCL1], 17477139\nCH5(T=473848)[S4M_VDD_CPUCL0], 113447446\nCH6(T=473848)[S5M_VDD_INT], 12543588\nCH7(T=473848)[S1M_VDD_MIF], 25901660\n"; + let input = parsec::BytesInput::new(input); + let mut samples = Vec::new(); + let mut f = |sample| { + samples.push(sample); + Ok(()) + }; + let mut parser = emeter_parser(&mut f); + let res = parser.parse(input); + res.unwrap_success(); + + let expected = vec![ + Sample { + ts: 473848, + value: 3161249, + chan: 42, + chan_name: "S10M_VDD_TPU".into(), + }, + Sample { + ts: 473848, + value: 48480309, + chan: 1, + chan_name: "VSYS_PWR_MODEM".into(), + }, + Sample { + ts: 473848, + value: 9594393, + chan: 2, + chan_name: "VSYS_PWR_RFFE".into(), + }, + Sample { + ts: 473848, + value: 28071872, + chan: 3, + chan_name: "S2M_VDD_CPUCL2".into(), + }, + Sample { + ts: 473848, + value: 17477139, + chan: 4, + chan_name: "S3M_VDD_CPUCL1".into(), + }, + Sample { + ts: 473848, + value: 113447446, + chan: 5, + chan_name: "S4M_VDD_CPUCL0".into(), + }, + Sample { + ts: 473848, + value: 12543588, + chan: 6, + chan_name: "S5M_VDD_INT".into(), + }, + Sample { + ts: 473848, + value: 25901660, + chan: 7, + chan_name: "S1M_VDD_MIF".into(), + }, + ]; + + drop(parser); + for (_sample, _expected) in samples.into_iter().zip(expected) { + assert_eq!(_sample, _expected); + } + } + + #[test] + fn test_emeter_row_parser() { + let input = b"CH42(T=473848)[S10M_VDD_TPU], 3161249"; + let input = parsec::BytesInput::new(input); + let mut sample = None; + let mut f = |_sample| { + sample = Some(_sample); + Ok(()) + }; + let mut parser = emeter_row_parser(&mut f); + let res = parser.parse(input); + let _ = res.unwrap_success(); + drop(parser); + let parsed = sample.unwrap(); + let expected = Sample { + ts: 473848, + value: 3161249, + chan: 42, + chan_name: "S10M_VDD_TPU".into(), + }; + assert_eq!(parsed, expected); + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index 4aa35e45f..ad240b9e0 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -15,6 +15,10 @@ extern crate alloc; +// Allow std in unit tests for convenience, so we can use e.g. println!() +#[cfg(test)] +extern crate std; + pub mod error; pub mod features; pub mod fmt; -- GitLab