diff --git a/lisa/_assets/kmodules/lisa/Makefile b/lisa/_assets/kmodules/lisa/Makefile index 11693a2843128e6bb2a189ee0bd3f36085c50978..5d53e9d9438b7234eaed1a500c36e7294784a791 100644 --- a/lisa/_assets/kmodules/lisa/Makefile +++ b/lisa/_assets/kmodules/lisa/Makefile @@ -71,6 +71,8 @@ clean-files := $(GENERATED) RUST_VERSION ?= nightly RUSTFLAGS := -Clinker=rust-lld -Clink-self-contained=y -Clto=n -Crelocation-model=static -Cno-redzone=y -Csoft-float=y -Ctarget-cpu=generic -Cforce-frame-pointers=y -Ccodegen-units=1 -Zfunction-sections=y +LISA_EVAL_C := $(MODULE_SRC)/lisa-eval-c.sh + RUST_SRC := $(MODULE_SRC)/rust/lisakmod RUST_LDS := $(RUST_SRC)/rust.lds RUST_GENERATED := $(GENERATED)/rust @@ -78,6 +80,7 @@ RUST_OBJECT := $(RUST_GENERATED)/rust.o RUST_OBJECT_CMD := $(RUST_GENERATED)/.rust.o.cmd RUST_CBINDGEN_H := $(RUST_SRC)/cbindgen.h RUST_C_SHIMS_H := $(RUST_GENERATED)/rust_c_shims.h +RUST_C_SHIMS_C := $(RUST_GENERATED)/rust_c_shims.c RUST_C_SHIMS_DIR := $(RUST_GENERATED)/rust_c_shims RUSTUP_HOME ?= $(HOME)/.rustup @@ -89,9 +92,20 @@ RUST_SYMBOLS_CLI := $(RUST_BUILD_DIR)/exported.cli RUST_TARGET_DIR := $(RUST_BUILD_DIR)/target RUST_CBINDGEN_BIN := $(CARGO_HOME)/bin/cbindgen +# Export c_flags so that lisa-eval-c.sh can compile a C source with the +# appropriate flags. Trying to pass down $(c_flags) via parameters or +# manually-exported env var triggers a nightmare of shell quoting as some of +# the compiler flags have quotes that are significant (e.g. ARM64_ASM_ARCH that +# needs to have double quotes in the value to be a well-formed C string +# literal). +# +# We need to expand the values once to mimic what seems to be happening in +# Kbuild. +export LISA_EVAL_C_CFLAGS=$(shell echo $(c_flags)) + # Enable RUSTC_BOOTSTRAP=1 so we can use nightly features on any toolchain, # including stable ones. This allows using the more tested stable binaries. -rust_cmd = export RUSTC_BOOTSTRAP=1 PATH="$(CARGO_HOME)/bin:$$PATH" RUSTUP_HOME=$(RUSTUP_HOME) CARGO_HOME=$(CARGO_HOME) && $(1) +rust_cmd = chmod +x '$(LISA_EVAL_C)' && export RUSTC_BOOTSTRAP=1 PATH="$(CARGO_HOME)/bin:$$PATH" 'RUSTUP_HOME=$(RUSTUP_HOME)' 'CARGO_HOME=$(CARGO_HOME)' 'LISA_EVAL_C=$(LISA_EVAL_C)' && $(1) cargo_cmd = $(call rust_cmd,cargo +$(RUST_VERSION) $(1)) $(RUST_GENERATED): $(GENERATED) @@ -106,26 +120,24 @@ $(RUST_TARGET_DIR): $(RUST_BUILD_DIR) # Build the rust code into a static archive, then prelink it into an object # file and filter the symbols to only keep as GLOBAL the exported pub # #[no_mangle] ones. This avoids having tons of GLOBAL symbols from core lib. -$(RUST_OBJECT) $(RUST_C_SHIMS_H): $(RUST_TARGET_DIR) +$(RUST_OBJECT) $(RUST_C_SHIMS_H) $(RUST_C_SHIMS_C): $(RUST_TARGET_DIR) # Build the crate into a static library cd $(RUST_SRC) && export RUSTFLAGS="$(RUSTFLAGS)" && $(call cargo_cmd,build --target-dir $(RUST_TARGET_DIR) --release --target=$(RUST_SRC)/rustc_targets/$(ARCH)/target.json -Zbuild-std=core$(comma)alloc) - # Generate the documentation JSON that we will use to extract symbols to keep - cd $(RUST_SRC) && $(call cargo_cmd,rustdoc --release --target-dir $(RUST_TARGET_DIR) -- -Zunstable-options --output-format json) - - # Get the list of exported symbols (pub with #[no_mangle] attribute) from the rustdoc JSON - python3 "$(MODULE_SRC)/rustdoc_symbols.py" --rustdoc-json $(RUST_TARGET_DIR)/doc/lisakmod.json --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) - cat $(RUST_SYMBOLS) # Prelink the archive into a single object file. $(LD) $(KBUILD_LDFLAGS) -nostdlib -r -o $(RUST_OBJECT) --whole-archive $(RUST_TARGET_DIR)/target/release/liblisakmod.a + # Get the list of exported symbols + python3 "$(MODULE_SRC)/rust_exported_symbols.py" --rust-object $(RUST_OBJECT) --out-symbols-plain $(RUST_SYMBOLS) --out-symbols-cli $(RUST_SYMBOLS_CLI) + # Extract the C shims created by our inlinec Rust module from the # binary, so they can be compiled and linked later. mkdir -p "$(RUST_C_SHIMS_DIR)" - touch $(RUST_C_SHIMS_DIR)/empty - $(OBJDUMP) -h $(RUST_OBJECT) | awk '{ print $$2 }' | grep '^.binstore.c_shims.' | xargs -n1 sh -c '$(OBJCOPY) --dump-section=$$0=$(RUST_C_SHIMS_DIR)/shim_$$0 $(RUST_OBJECT)' - LC_ALL=C cat $(RUST_C_SHIMS_DIR)/* > $(RUST_C_SHIMS_H) - cat $(RUST_C_SHIMS_H) | if which clang-format 2>&1 1>/dev/null; then clang-format; else cat; fi + touch $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header. $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code. + $(OBJDUMP) -h $(RUST_OBJECT) | awk '{ print $$2 }' | grep '^.binstore.c.' | xargs -n1 sh -c '$(OBJCOPY) --dump-section $$0=$(RUST_C_SHIMS_DIR)/shim_$$0 $(RUST_OBJECT) /dev/null' + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.header.* > $(RUST_C_SHIMS_H) + LC_ALL=C cat $(RUST_C_SHIMS_DIR)/shim_.binstore.c.code.* > $(RUST_C_SHIMS_C) + cat $(RUST_C_SHIMS_H) $(RUST_C_SHIMS_C) | if which clang-format 2>&1 1>/dev/null; then clang-format; else cat; fi # Garbage collect unused sections in the object file, after we have # extracted the binstore sections (otherwise they get discarded). @@ -255,7 +267,7 @@ $(MODULE_VERSION_H): $(GENERATED) printf "#define LISA_MODULE_VERSION \"" >> $@ # Show the content of the folder to debug mismatching sha1sum issues ls -laR $(MODULE_SRC) - export LC_ALL=C && (cd $(MODULE_SRC) && find -type f '(' -name '*.c' -or -name '*.h' -or -name '*.rs' -or -name '*.toml' -or -name '*.txt' -or -name '*.json' -or -name '*.md' -or -name '*.py' -or -name '*.sh' -or -name '*.lds' -or -name 'Makefile' -or -name 'vmlinux' -or -name 'kallsyms' ')' -and -not -path './generated/*' -and -not -path './__pycache__/*' | xargs sha1sum) | sort | sha1sum | cut -d' ' -f1 | tr -d '\n' >> $@ + export LC_ALL=C && (cd $(MODULE_SRC) && find -type f '(' -name '*.c' -or -name '*.h' -or -name '*.rs' -or -name '*.toml' -or -name '*.lock' -or -name '*.txt' -or -name '*.json' -or -name '*.md' -or -name '*.py' -or -name '*.sh' -or -name '*.lds' -or -name 'Makefile' -or -name 'vmlinux' -or -name 'kallsyms' ')' -and -not -path './generated/*' -and -not -path './__pycache__/*' | xargs sha1sum) | sort | sha1sum | cut -d' ' -f1 | tr -d '\n' >> $@ printf "\"\n" >> $@ @@ -263,7 +275,7 @@ $(KALLSYMS): $(GENERATED) cat $(MODULE_SRC)/kallsyms > $@ || $(NM) $(_REAL_VMLINUX) > $@ || touch $@ # Make all object files depend on the generated sources -$(addprefix $(MODULE_OBJ)/,$($(LISA_KMOD_NAME)-y)): $(INTROSPECTION_DATA_H) $(SYMBOL_NAMESPACES_H) $(MODULE_VERSION_H) $(SYMBOLS_LDS) $(RUST_CBINDGEN_H) $(RUST_C_SHIMS_H) +$(addprefix $(MODULE_OBJ)/,$($(LISA_KMOD_NAME)-y)): $(INTROSPECTION_DATA_H) $(SYMBOL_NAMESPACES_H) $(MODULE_VERSION_H) $(SYMBOLS_LDS) $(RUST_CBINDGEN_H) $(RUST_C_SHIMS_H) $(RUST_C_SHIMS_C) # Non-Kbuild part else diff --git a/lisa/_assets/kmodules/lisa/lisa-eval-c.sh b/lisa/_assets/kmodules/lisa/lisa-eval-c.sh new file mode 100755 index 0000000000000000000000000000000000000000..74400f5ebfde4432c64519f2748ee1cee139e4ac --- /dev/null +++ b/lisa/_assets/kmodules/lisa/lisa-eval-c.sh @@ -0,0 +1,39 @@ +#! /bin/bash +# SPDX-License-Identifier: Apache-2.0 +# +# Evaluate a snippet of C code as an unsigned integer constant and return the +# value in hexadecimal format. + +c_headers=$1 +c_expr=$2 + +build=$(mktemp -d) +object=$build/object.o +src=$build/src.c + +cleanup() { + rm -r "$build" +} + +trap cleanup EXIT + +# Evaluate a C constant expression as the size of an array, and then later +# extract the symbol size from the symbol table after compiling. This is works +# reliably on any compiler and provides the value in hex format. + +cat > $src << EOM +$c_headers +static char __attribute__((used)) LISA_C_CONST_VALUE[$c_expr]; +EOM + +CC=${CC:=cc} +NM=${NM:=nm} + +if ("$CC" --version | grep -i clang) &>/dev/null; then + clang_args=(-Wno-error=unused-command-line-argument) +fi + +# All -I are relative to the kernel tree root, so we need to run from there. +cd "$KERNEL_SRC" && +$CC $LISA_EVAL_C_CFLAGS "${clang_args[@]}" -c "$src" -o "$object" && $NM -S "$object" | awk '{if ($4 == "LISA_C_CONST_VALUE") {print "0x" $2}}' + diff --git a/lisa/_assets/kmodules/lisa/main.c b/lisa/_assets/kmodules/lisa/main.c index ba99fc7b638e2198f728d4ec0292a210fa6bec51..bfa9c27bb348199686d4932d0d521d8bfc79e07c 100644 --- a/lisa/_assets/kmodules/lisa/main.c +++ b/lisa/_assets/kmodules/lisa/main.c @@ -4,8 +4,8 @@ #include "main.h" #include "features.h" #include "introspection.h" -#include "rust/lisakmod/tests.h" #include "generated/module_version.h" +#include "rust/lisakmod/bindings.h" /* Import all the symbol namespaces that appear to be defined in the kernel * sources so that we won't trigger any warning */ @@ -23,6 +23,8 @@ MODULE_PARM_DESC(features, "Comma-separated list of features to enable. Availabl static void modexit(void) { if (deinit_features()) pr_err("Some errors happened while unloading LISA kernel module\n"); + + rust_mod_exit(); } static int __init modinit(void) { @@ -34,9 +36,9 @@ static int __init modinit(void) { return -EPROTO; } - ret = rust_tests(); + ret = rust_mod_init(); if (ret) { - pr_err("Lisa module Rust support validation failed: %i\n", ret); + pr_err("Lisa module Rust code failed to initialize properly: %i\n", ret); return -EINVAL; } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..106b02dab8ff8081dbfc1b5910f8e26f7936d214 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "lisakmod_macros" +version = "0.1.0" +edition = "2021" + +[dependencies] +lisakmod_macros_proc = { path = "./macros" } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h new file mode 100644 index 0000000000000000000000000000000000000000..09adb1711b4bdc4dcf44a398f4b51d7a87ced7ca --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/cffi.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include + +/// Types defined in this header have ABI compatibility with some types +/// manipulated in lisakmod_macros::inlinec module. +/// +/// They must therefore be kept in sync to avoid any undefined behavior. + +#ifndef _CFFI_H +#define _CFFI_H + +struct slice_u8 { + uint8_t *data; + size_t len; +}; + +struct slice_const_u8 { + const uint8_t *data; + const size_t len; +}; + +#endif /* _CFFI_H */ diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml similarity index 76% rename from lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml rename to lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml index 971db75badab181d546e59e2965fcc04fdc9b069..6e2b94bb12965cb7bb65cbe71fc164c29cfb0596 100644 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "lisakmodprocmacro_macro" +name = "lisakmod_macros_proc" version = "0.1.0" edition = "2021" [dependencies] -syn = { version = "2.0", default-features = false, features = ["proc-macro", "parsing", "printing", "full", "clone-impls", "extra-traits"]} +syn = { version = "2.0", default-features = false, features = ["proc-macro", "parsing", "printing", "full", "clone-impls"]} quote = { version = "1.0", default-features = false } proc-macro2 = { version = "1.0", default-features = false, features = ["span-locations"]} getrandom = {version = "0.2", default-features = false } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3ad106b7c2928bcb6835b42f8299e5634ffd1f3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/inlinec.rs @@ -0,0 +1,625 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use std::io::{self, Write}; + +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{ + parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, token, Abi, Attribute, + Error, Expr, ExprGroup, ExprLit, ExprTuple, FnArg, Generics, Ident, ItemFn, Lit, LitStr, Meta, + Pat, ReturnType, Stmt, StmtMacro, Token, Type, Visibility, +}; + +use crate::misc::{concatcp, get_random}; + +struct CFuncInput { + name: Ident, + c_code: ( + Option, + Option, + Option, + ), + f_args: Vec<(Ident, Type)>, + f_attrs: Vec, + f_ret_ty: Type, + f_generics: Generics, + f_unsafety: Option, +} + +fn get_f_args(item_fn: &ItemFn) -> syn::Result> { + item_fn + .sig + .inputs + .iter() + .map(|arg| match arg { + FnArg::Typed(arg) => { + let ident = match &*arg.pat { + Pat::Ident(ident) => Ok(ident.ident.clone()), + _ => Err(Error::new( + arg.span(), + "An inline C function parameter must be an identifier.", + )), + }?; + Ok((ident, *arg.ty.clone())) + } + FnArg::Receiver(..) => Err(Error::new( + arg.span(), + "self is not allowed in inline C function parameter", + )), + }) + .collect::>() +} + +fn get_f_ret_ty(item_fn: &ItemFn) -> syn::Result { + Ok(match &item_fn.sig.output { + ReturnType::Type(_, ty) => *ty.clone(), + ReturnType::Default => parse_quote!(()), + }) +} + +impl Parse for CFuncInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let item_fn: ItemFn = input.parse()?; + let span = item_fn.span(); + let f_args = get_f_args(&item_fn)?; + let f_ret_ty = get_f_ret_ty(&item_fn)?; + let f_attrs = item_fn.attrs; + let name = item_fn.sig.ident; + + let mut snippets = Vec::new(); + for stmt in item_fn.block.stmts { + let snippet = match &stmt { + Stmt::Expr(expr, _) => Ok(Stmt::Expr(expr.clone(), None)), + Stmt::Macro(mac) => { + let mac = mac.clone(); + Ok(Stmt::Macro(StmtMacro { + semi_token: None, + ..mac + })) + } + stmt => Err(Error::new( + stmt.span(), + "An inline C function must contain string expressions.", + )), + }?; + snippets.push(quote! {#snippet}); + } + + let c_code = match snippets.len() { + 1 => Ok(( + None, + Some(snippets[0].clone()), + None, + )), + 2 => Ok(( + Some(snippets[0].clone()), + Some(snippets[1].clone()), + None, + )), + 3 => Ok(( + Some(snippets[0].clone()), + Some(snippets[1].clone()), + Some(snippets[2].clone()), + )), + _ => Err(Error::new(span, "An inline C function must contain either a single string expression with the C code in it or two string expressions (one output before the function and one for the body).")) + }?; + + let f_generics = item_fn.sig.generics.clone(); + let f_unsafety = item_fn.sig.unsafety; + + Ok(Self { + name, + c_code, + f_args, + f_attrs, + f_ret_ty, + f_generics, + f_unsafety, + }) + } +} + +fn make_c_func( + rust_name: Option<&Ident>, + c_name: &Ident, + f_generics: Option<&Generics>, + f_args: &[(Ident, Type)], + f_ret_ty: &Type, + c_code: ( + Option, + Option, + Option, + ), +) -> Result { + let (c_out, c_header_out, rust_out) = + _make_c_func(rust_name, c_name, f_generics, f_args, f_ret_ty, c_code)?; + + let outs = [c_out, c_header_out]; + let sections = [ + format!(".binstore.c.code.{}", c_name), + format!(".binstore.c.header.{}", c_name), + ]; + + Ok(quote! { + // Store the C function in a section of the binary, that will be extracted by the + // module Makefile and compiled separately as C code. + #( + const _: () = { + const CODE_SLICE: &[u8] = #outs.as_bytes(); + const CODE_LEN: usize = CODE_SLICE.len(); + + #[link_section = #sections ] + #[used] + static CODE: [u8; CODE_LEN] = ::lisakmod_macros::private::misc::slice_to_array::<{CODE_LEN}>(CODE_SLICE); + }; + )* + + #rust_out + }) +} + +fn _make_c_func( + rust_name: Option<&Ident>, + c_name: &Ident, + f_generics: Option<&Generics>, + f_args: &[(Ident, Type)], + f_ret_ty: &Type, + c_code: ( + Option, + Option, + Option, + ), +) -> Result<(TokenStream, TokenStream, TokenStream), Error> { + let c_name_str: String = c_name.to_string(); + let f_where = f_generics.map(|x| x.where_clause.clone()); + let c_proto = format!("{c_name_str}_proto"); + let c_ret_ty = format!("{c_name_str}_ret_ty"); + + let (pre_c_code, c_code, post_c_code) = c_code; + // TODO: Due to this issue, the span reported by syn is currently always going to have + // span.line == 0, so we workaround that by just emitting a line!() call. This is not as + // precise though. + // https://github.com/dtolnay/proc-macro2/issues/402 + // EDIT: the issue is fixed, let's keep the workaround at hand in case it is needed again as + // this was originally disabled because the rustc API was in flux. + // + // let pre_c_code_line = quote! { line!() }; + // let c_code_line = quote! { line!() }; + // let post_c_code_line = quote! { line!() }; + let pre_c_code_line = match &pre_c_code { + Some(tokens) => tokens.span().start().line, + None => 0, + } + .to_string(); + let c_code_line = match &c_code { + Some(tokens) => tokens.span().start().line.to_string(), + None => pre_c_code_line.clone(), + }; + let post_c_code_line = match &post_c_code { + Some(tokens) => tokens.span().start().line.to_string(), + None => c_code_line.clone(), + }; + + let c_nr_args = f_args.len(); + let (arg_names, arg_tys): (Vec<_>, Vec<_>) = f_args.iter().cloned().unzip(); + let c_args_name: Vec<_> = arg_names.iter().map(|name| name.to_string()).collect(); + let c_args_commas: Vec<_> = c_args_name + .iter() + .enumerate() + .map(|(i, _)| if i == (c_nr_args - 1) { "" } else { ", " }) + .collect(); + let (c_args_ty_macro, c_args_ty_typedef): (Vec<_>, Vec<_>) = arg_names + .iter() + .map(|arg| { + ( + format!("{c_name_str}_arg_ty_macro_{arg}"), + format!("{c_name_str}_arg_ty_typedef_{arg}"), + ) + }) + .unzip(); + + // Argument types are encoded as a function-like macro. Calling this macro with an + // identifier declares a variable (or function argument) of that type. This allows using + // any type, including more complex ones like arrays and function pointers. + let c_args_typedef = concatcp(quote! { + #( + "\n", + "#define ", #c_args_ty_macro, "(DECLARATOR)", + { + // Use a const fn to introduce f_generics and f_where + const fn get #f_generics() -> &'static str #f_where { + <#arg_tys as ::lisakmod_macros::inlinec::FfiType>::C_DECL + } + get() + }, + "\n", + "typedef ", #c_args_ty_macro, "(", #c_args_ty_typedef ,");" + ),* + })?; + + let c_args = if c_nr_args == 0 { + quote! { "void "} + } else { + concatcp(quote! { + #( + #c_args_ty_typedef, " ", #c_args_name, + #c_args_commas + ),* + })? + }; + + let rust_extern_args = quote! { + #( + #arg_names : <#arg_tys as ::lisakmod_macros::inlinec::FfiType>::FfiType + ),* + }; + + let pre_c_code = pre_c_code.unwrap_or(quote! {""}); + let post_c_code = post_c_code.unwrap_or(quote! {""}); + + let c_funcdef = match c_code { + Some(c_code) => concatcp(quote! { + #c_proto, + "{\n#line ", #c_code_line, " \"", file!(), "\"\n", + #c_code, + "\n}\n", + })?, + None => quote! {""}, + }; + + let c_header_out = concatcp(quote! { + r#" + #include + + #define BUILTIN_TY_DECL(ty, declarator) ty declarator + #define PTR_TY_DECL(declarator) (*declarator) + #define ARR_TY_DECL(N, declarator) ((declarator)[N]) + #define CONST_TY_DECL(declarator) const declarator + #define ATTR_TY_DECL(attributes, declarator) attributes declarator + #define FN_TY_DECL(args, declarator) ((declarator)args) + "#, + + "#line ", #c_code_line, " \"", file!(), "\"\n", + #c_args_typedef, + + // See comment on how arguments type are handled, as we do the same for the return + // type. + "\n#define ", #c_ret_ty, "(DECLARATOR)", + { + // Use a const fn to introduce f_generics and f_where + const fn get #f_generics() -> &'static str #f_where { + <#f_ret_ty as ::lisakmod_macros::inlinec::FfiType>::C_DECL + } + get() + }, + "\n#define ", #c_proto, " ", #c_ret_ty, "(FN_TY_DECL((", #c_args, "), ATTR_TY_DECL(__nocfi, ", #c_name_str, ")))", + "\n#line ", #c_code_line, " \"", file!(), "\"\n", + #c_proto, ";\n", + })?; + + let c_out = concatcp(quote! { + "#line ", #pre_c_code_line, " \"", file!(), "\"\n", + #pre_c_code, + "\n", + // Definition + "#line ", #c_code_line, " \"", file!(), "\"\n", + #c_funcdef, + "#line ", #post_c_code_line, " \"", file!(), "\"\n", + #post_c_code, + "\n", + })?; + + let rust_out = match rust_name { + Some(rust_name) => quote! { + extern "C" { + #[link_name = #c_name_str] + fn #rust_name #f_generics(#rust_extern_args) -> <#f_ret_ty as ::lisakmod_macros::inlinec::FfiType>::FfiType #f_where; + } + }, + None => quote! {}, + }; + Ok((c_out, c_header_out, rust_out)) +} + +pub fn cfunc(_attrs: TokenStream, code: TokenStream) -> Result { + let input = syn::parse2::(code)?; + + let name = input.name; + let f_ret_ty = input.f_ret_ty; + let f_attrs = input.f_attrs; + let f_args = input.f_args; + let f_unsafety = input.f_unsafety; + let f_generics = input.f_generics; + let f_where = f_generics.where_clause.clone(); + + let shim_name_str = format!("__lisa_c_shim_{name}_{}", get_random()); + let shim_name = format_ident!("{}", shim_name_str); + let c_out = make_c_func( + Some(&shim_name), + &shim_name, + Some(&f_generics), + &f_args, + &f_ret_ty, + input.c_code, + )?; + + let (arg_names, arg_tys): (Vec<_>, Vec<_>) = f_args.into_iter().unzip(); + let rust_args = quote! { + #( + #arg_names : #arg_tys + ),* + }; + let rust_extern_call_args = quote! { + #( + ::lisakmod_macros::inlinec::IntoFfi::into_ffi(#arg_names) + ),* + }; + + let rust_out = quote! { + #[inline] + #(#f_attrs)* + #f_unsafety fn #name #f_generics(#rust_args) -> #f_ret_ty #f_where { + unsafe { + ::lisakmod_macros::inlinec::FromFfi::from_ffi( + #shim_name(#rust_extern_call_args) + ) + } + } + }; + + let out = quote! { + #c_out + #rust_out + }; + // eprintln!("{}", &out.to_string()); + Ok(out) +} + +pub fn c_constant(args: TokenStream) -> Result { + let span = Span::call_site(); + let args = syn::parse::Parser::parse2(Punctuated::::parse_terminated, args)?; + + fn parse_arg(expr: Expr) -> Result { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => Ok(s.value()), + Expr::Tuple(ExprTuple { elems, .. }) => { + let elems = elems + .into_iter() + .map(parse_arg) + .collect::, _>>()?; + Ok(elems.join("")) + } + Expr::Group(ExprGroup { expr, .. }) => parse_arg(*expr), + // expr => panic!("{:?}", expr), + expr => Err(Error::new(expr.span(), "Expected a string literal")), + } + } + + let args: Vec<_> = args + .into_iter() + .map(parse_arg) + .collect::, _>>()?; + + let (headers, expr, default) = if args.len() == 3 { + Ok((args[0].clone(), args[1].clone(), args[2].clone())) + } else { + Err(Error::new( + span, + "Expected 3 string literals: the header #includes, the expression, and a default value (as a C expression string) when no C toolchain is available (e.g. when running cargo check).", + )) + }?; + + let out = match std::env::var("LISA_EVAL_C") { + Ok(cmd) => { + use std::process::Command; + let mut cmd = Command::new(cmd); + + cmd.arg(headers.clone()).arg(expr.clone()); + let out = cmd + .output() + .map_err(|e| Error::new(span, format!("Could not run C toolchain: {e}")))?; + + io::stderr().write_all(&out.stderr).unwrap(); + + let out = std::str::from_utf8(&out.stdout) + .map_err(|_| Error::new(span, "Could not decode toolchain output"))?; + out.trim().to_owned() + } + Err(_) => default.to_owned(), + }; + + let out_expr = syn::parse_str::(&out)?; + let assert_cond = concatcp(quote! { + "(", #expr, ") == (", #out, ")" + })?; + let out = quote! { + { + // Ensure that the constant value we got from the compiler matches what we will get in + // the real world when running the code. + ::lisakmod_macros::inlinec::c_static_assert!( + #headers, + #assert_cond + ); + #out_expr + } + }; + Ok(out) +} + +struct CExportInput { + item_fn: ItemFn, +} + +impl Parse for CExportInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let item_fn: ItemFn = input.parse()?; + Ok(CExportInput { item_fn }) + } +} + +struct CExportAttrs { + link_name: Option, +} + +impl Parse for CExportAttrs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs = Punctuated::::parse_terminated(input)?; + + let mut link_name = None; + for attr in attrs { + match attr { + Meta::List(ml) => match ml.path.get_ident() { + Some(ident) => match &*ident.to_string() { + "link_name" => { + link_name = Some(syn::parse2::(ml.tokens)?.value()); + } + name => { + return Err(Error::new(ml.span(), format!("Unknown argument: {name}"))) + } + }, + _ => return Err(Error::new(ml.span(), "Invalid argument name".to_string())), + }, + attr => return Err(Error::new(attr.span(), "Unknown argument".to_string())), + } + } + + Ok(CExportAttrs { link_name }) + } +} + +pub fn cexport(attrs: TokenStream, code: TokenStream) -> Result { + let input = syn::parse2::(code)?; + let attrs = syn::parse2::(attrs)?; + let mut item_fn = input.item_fn; + let f_args = get_f_args(&item_fn)?; + let f_ret_ty = get_f_ret_ty(&item_fn)?; + let (arg_names, arg_tys): (Vec<_>, Vec<_>) = f_args.iter().cloned().unzip(); + + fn ffi_ty(ty: Type) -> syn::Result { + Ok(parse_quote! { + <#ty as ::lisakmod_macros::inlinec::FfiType>::FfiType + }) + } + + let new_args = item_fn + .sig + .inputs + .into_iter() + .map(|arg| match arg { + FnArg::Typed(mut arg) => { + arg.ty = Box::new(ffi_ty(*arg.ty.clone())?); + Ok(FnArg::Typed(arg)) + } + FnArg::Receiver(..) => Err(Error::new( + arg.span(), + "self is not allowed in inline C function parameter", + )), + }) + .collect::>()?; + item_fn.sig.inputs = new_args; + + let ret_ty = get_f_ret_ty(&item_fn)?; + item_fn.sig.output = ReturnType::Type(Default::default(), Box::new(ffi_ty(ret_ty.clone())?)); + + let body = item_fn.block; + item_fn.block = Box::new(parse_quote! {{ + #( + let #arg_names: #arg_tys = ::lisakmod_macros::inlinec::FromFfi::from_ffi(#arg_names); + );* + <#ret_ty as ::lisakmod_macros::inlinec::IntoFfi>::into_ffi( + #body + ) + }}); + + let name = item_fn.sig.ident; + let rust_name = format_ident!("__lisa_rust_shim_{name}_{}", get_random()); + item_fn.sig.ident = rust_name.clone(); + + // Make the Rust function unsafe since we call FromFfi::from_ffi() + item_fn.sig.unsafety = Some(Default::default()); + item_fn.sig.abi = Some(Abi { + extern_token: Default::default(), + name: Some(LitStr::new("C", item_fn.span())), + }); + item_fn.vis = Visibility::Public(Default::default()); + + // Emit a C prototype for the Rust function, so that the C shim can call the Rust code. + let (_, rust_func_c_proto, _) = _make_c_func( + None, + &rust_name, + None, + &f_args, + &f_ret_ty, + (None, None, None), + )?; + + let call_rust_func = concatcp(quote! { + "return ", stringify!(#rust_name), "(", + #( + stringify!(#arg_names), + )* + ");" + })?; + + // We create a symbol picked up by rust_exported_symbols.py + let mut export_markers = vec![_export_symbol(rust_name)?]; + + // Give a unique name to the actual C function generated by default, so it cannot conflict with + // anything else. It will appear as "name" in the Rust universe thanks to the #[link_name] + // attribute. + let c_name = match attrs.link_name { + None => format!("__lisa_c_shim_{name}_{}", get_random()), + Some(name) => { + export_markers.push(_export_symbol(format_ident!("{name}"))?); + name + } + }; + + let c_name = format_ident!("{c_name}"); + + // Make a C function with the FfiType types, so that it is ABI-compatible with the Rust + // function we created. That C function is kind of useless, except in that it is C instead of + // Rust and therefore is a valid target for function pointers when kCFI is enabled + // (CONFIG_CFI_CLANG). + let c_out = make_c_func( + Some(&name), + &c_name, + None, + &f_args, + &f_ret_ty, + (Some(rust_func_c_proto), Some(call_rust_func), None), + )?; + + let out = quote! { + #( + #export_markers + )* + + #c_out + + // The Rust function needs to be callable by the C code, so we need a non-mangled name. + // However, we already added a unique cookie to that name, so there is no risk of clash. + #[no_mangle] + #item_fn + }; + Ok(out) +} + +pub fn export_symbol(args: TokenStream) -> Result { + let ident = syn::parse2::(args)?; + _export_symbol(ident).map(Into::into) +} + +fn _export_symbol(ident: Ident) -> Result { + let marker = format_ident!("__export_rust_symbol_{ident}"); + + Ok(quote! { + const _:() = { + #[used] + #[no_mangle] + static #marker: () = (); + }; + }) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..31f2c256031038979c25a8f7f967b63bbf79ff90 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/lib.rs @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use proc_macro::TokenStream; +use syn::Error; + +mod inlinec; +mod misc; + +fn convert(out: Result) -> proc_macro::TokenStream { + match out { + Err(err) => Error::into_compile_error(err).into(), + Ok(tokens) => tokens.into(), + } +} + +#[proc_macro_attribute] +pub fn cfunc(attrs: TokenStream, code: TokenStream) -> TokenStream { + convert(inlinec::cfunc(attrs.into(), code.into())) +} + +#[proc_macro_attribute] +pub fn cexport(attrs: TokenStream, code: TokenStream) -> TokenStream { + convert(inlinec::cexport(attrs.into(), code.into())) +} + +#[proc_macro] +pub fn c_constant(args: TokenStream) -> TokenStream { + convert(inlinec::c_constant(args.into())) +} + +#[proc_macro] +pub fn export_symbol(args: TokenStream) -> TokenStream { + convert(inlinec::export_symbol(args.into())) +} + +#[proc_macro] +pub fn concatcp(args: TokenStream) -> TokenStream { + convert(misc::concatcp(args.into())) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs new file mode 100644 index 0000000000000000000000000000000000000000..e15b53d262eb672ff687e7e656d324c534343d93 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/macros/src/misc.rs @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{punctuated::Punctuated, Error, Expr, Ident, Token}; + +// Use getrandom instead of e.g. uuid crate as it has far fewer dependencies, so faster build +// time. +pub(crate) fn get_random() -> u128 { + let mut buf: [u8; 128 / 8] = [0; 128 / 8]; + getrandom::getrandom(&mut buf).expect("Could not get random number"); + u128::from_le_bytes(buf) +} + +pub(crate) fn concatcp(args: TokenStream) -> Result { + let items = if args.is_empty() { + Vec::new() + } else { + syn::parse::Parser::parse2( + Punctuated::::parse_terminated, + args.clone(), + )? + .into_iter() + .collect() + }; + + let item_names: Vec = items + .iter() + .map(|_| format_ident!("__concat_item_{}", get_random())) + .collect(); + + let out = quote! { + { + #( + const #item_names: &'static str = #items; + )* + + unsafe { + ::core::str::from_utf8_unchecked( + &::lisakmod_macros::private::misc::concat::< + { + #( + #item_names.len() + + )* + 0 + } + >( + &[ + #( + #item_names + ),* + ] + ) + ) + } + } + }; + // eprintln!("OUT {out}"); + Ok(out) +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs new file mode 100644 index 0000000000000000000000000000000000000000..eda33651e2ef962948d98a92cda68fbdc11ff541 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/inlinec.rs @@ -0,0 +1,987 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use alloc::{boxed::Box, sync::Arc}; +use core::{ + alloc::Layout, + cell::UnsafeCell, + convert::Infallible, + ffi::{c_char, c_int, c_void, CStr}, + mem::MaybeUninit, + ops::Deref, + pin::Pin, + ptr::{null, null_mut, NonNull}, +}; + +pub use lisakmod_macros_proc::{c_constant, cexport, cfunc}; + +pub trait FfiType { + // TODO: if and when Rust gains const trait methods, we can just define a const function to + // build a type rather than providing a C macro body and C preprocessor machinery to build full + // type names. + const C_DECL: &'static str; + type FfiType; +} + +pub trait FromFfi: FfiType { + /// # Safety + /// Implementations must take as many precautions as possible not to trigger any undefined + /// behavior when converting from the C representation to Rust. For example, a Rust reference + /// can never be NULL, so this should be checked by the implementation and panic if necessary. + /// Obviously, it's impossible to provide compiler-checked guarantees about C code that would + /// provide the Rust level of safety, so this is on a best-effort basis. + unsafe fn from_ffi(x: Self::FfiType) -> Self; +} + +pub trait IntoFfi: FfiType { + fn into_ffi(self) -> Self::FfiType; +} + +/// [*const T] newtype. +/// +/// [FfiType], [FromFfi] and [IntoFfi] implementations on [ConstPtr] are expected to be provided +/// by the user. These implementations will in turn be used by blanket implementations to provide +/// those traits for: +/// * [*const T] +/// * [&'a T] +#[fundamental] +pub struct ConstPtr(*const T); + +impl From> for *const T { + #[inline] + fn from(x: ConstPtr) -> *const T { + x.0 + } +} + +impl FromFfi for ConstPtr +where + ConstPtr: FfiType, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + ConstPtr(x) + } +} + +impl IntoFfi for ConstPtr +where + ConstPtr: FfiType, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + self.0 + } +} + +/// [*mut T] newtype. +/// +/// [FfiType], [FromFfi] and [IntoFfi] implementations on [MutPtr] are expected to be provided +/// by the user. These implementations will in turn be used by blanket implementations to provide +/// those traits for: +/// * [*mut T] +/// * [&'a mut T] +/// * [NonNull] +/// * [Option>] +#[fundamental] +pub struct MutPtr(*mut T); + +impl From> for *mut T { + #[inline] + fn from(x: MutPtr) -> *mut T { + x.0 + } +} + +impl FromFfi for MutPtr +where + T: ?Sized, + MutPtr: FfiType, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + MutPtr(x) + } +} + +impl IntoFfi for MutPtr +where + T: ?Sized, + MutPtr: FfiType, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + self.0 + } +} + +// The user-provided "root" implementation is for &T and &mut T. Everything is provided from there. +// This is because &T and &mut T are fundamental (as in #[fundamental]), meaning that even if the +// "reference of" generic type is not provided by the crate (since it's a primitive), the user is +// free to implement traits for it despite the regular orphan rule. *T, *mut T, NonNull etc do +// not enjoy such treatment, making it impossible to provide implementation for foreign traits. +impl FfiType for *const T +where + T: ?Sized, + ConstPtr: FfiType, +{ + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl FromFfi for *const T +where + T: ?Sized, + ConstPtr: FfiType + FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr: ConstPtr = FromFfi::from_ffi(x); + ptr.0 + } +} + +impl IntoFfi for *const T +where + T: ?Sized, + ConstPtr: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + ConstPtr(self).into_ffi() + } +} + +// These implementations allow arbitrary nesting of pointers, as they provide the definition for a +// pointer to pointer. Unfortunately, we cannot provide a valid generic C_DECL for that, so we +// currently cannot express this. + +// impl FfiType for ConstPtr> +// where +// T: ?Sized, +// ConstPtr: FfiType, +// { +// const C_DECL: &'static str = as FfiType>::C_DECL; +// type FfiType = *const as FfiType>::FfiType; +// } + +// impl FfiType for ConstPtr> +// where +// T: ?Sized, +// MutPtr: FfiType, +// { +// const C_DECL: &'static str = as FfiType>::C_DECL; +// type FfiType = *const as FfiType>::FfiType; +// } + +impl FfiType for ConstPtr<*const T> +where + T: ?Sized, + ConstPtr>: FfiType, +{ + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl FfiType for ConstPtr<*mut T> +where + T: ?Sized, + ConstPtr>: FfiType, +{ + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl FfiType for *mut T +where + T: ?Sized, + MutPtr: FfiType, +{ + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl FromFfi for *mut T +where + T: ?Sized, + MutPtr: FfiType + FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr: MutPtr = FromFfi::from_ffi(x); + ptr.0 + } +} + +impl FfiType for MutPtr<*const T> +where + T: ?Sized, + MutPtr>: FfiType, +{ + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl FfiType for MutPtr<*mut T> +where + T: ?Sized, + MutPtr>: FfiType, +{ + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl IntoFfi for *mut T +where + T: ?Sized, + MutPtr: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + MutPtr(self).into_ffi() + } +} + +trait PtrToMaybeSized { + #[inline] + fn is_aligned(&self) -> Option { + let ptr = self.as_ptr(); + // SAFETY: Unstable API, so the safety requirements might change, but overall the + // expectation is that it won't bring more UB than using a pointer without checking whether + // it was aligned or not. + let layout = unsafe { Layout::for_value_raw(ptr) }; + let addr: usize = ptr as *const u8 as usize; + Some((addr % layout.align()) == 0) + } + fn as_ptr(&self) -> *const T; +} + +impl PtrToMaybeSized for *const T { + #[inline] + fn as_ptr(&self) -> *const T { + *self + } +} + +impl PtrToMaybeSized for *mut T { + #[inline] + fn as_ptr(&self) -> *const T { + *self + } +} + +impl FfiType for &T +where + T: ?Sized, + *const T: FfiType + PtrToMaybeSized, +{ + const C_DECL: &'static str = <*const T as FfiType>::C_DECL; + type FfiType = <*const T as FfiType>::FfiType; +} + +impl FromFfi for &T +where + T: ?Sized, + *const T: FfiType + FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr = <*const T as FromFfi>::from_ffi(x); + assert!(PtrToMaybeSized::is_aligned(&ptr).unwrap_or(true)); + ptr.as_ref().expect("Unexpected NULL pointer") + } +} + +impl IntoFfi for &T +where + T: ?Sized, + *const T: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + (self as *const T).into_ffi() + } +} + +impl FfiType for &mut T +where + T: ?Sized, + *mut T: FfiType, +{ + const C_DECL: &'static str = <*mut T as FfiType>::C_DECL; + type FfiType = <*mut T as FfiType>::FfiType; +} + +impl FromFfi for &mut T +where + T: ?Sized, + *mut T: FfiType + FromFfi + PtrToMaybeSized, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr = <*mut T as FromFfi>::from_ffi(x); + assert!(PtrToMaybeSized::is_aligned(&ptr).unwrap_or(true)); + ptr.as_mut().expect("Unexpected NULL pointer") + } +} + +impl IntoFfi for &mut T +where + T: ?Sized, + *mut T: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + (self as *mut T).into_ffi() + } +} + +impl FfiType for ConstPtr> +where + T: ?Sized, + *const T: FfiType, + *mut T: FfiType, +{ + // Expose as a mutable pointer for C code, since the whole point of UnsafeCell is to allow + // mutation of T from a &UnsafeCell. + const C_DECL: &'static str = <*mut T as FfiType>::C_DECL; + // Expose the pointer as *const for the FFI functions so that the signatures are compatible + // with the other blanket implementations. This will effectively transmute the *const + // UnsafeCell into *mut T in the IntoFfi implementation at the FFI boundary. + type FfiType = *const UnsafeCell; +} + +impl FfiType for MutPtr> +where + T: ?Sized, + *mut T: FfiType, +{ + const C_DECL: &'static str = <*mut T as FfiType>::C_DECL; + type FfiType = *mut UnsafeCell; +} + +impl FfiType for Pin +where + T: FfiType, +{ + const C_DECL: &'static str = ::C_DECL; + type FfiType = ::FfiType; +} + +impl FromFfi for Pin +where + T: FromFfi + Deref, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let x: T = FromFfi::from_ffi(x); + // SAFETY: We get the value transferred from C to Rust, so C should not preserve ownership + // of the data. + unsafe { Pin::new_unchecked(x) } + } +} + +impl IntoFfi for Pin +where + T: IntoFfi + Deref, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + IntoFfi::into_ffi( + // SAFETY: It is the responsibility of the C code writer to ensure the Pin invariants + // are upheld once the data live in C land. + unsafe { Pin::into_inner_unchecked(self) }, + ) + } +} + +impl FfiType for ConstPtr> +where + *const T: FfiType, +{ + const C_DECL: &'static str = <*const T as FfiType>::C_DECL; + type FfiType = *const Pin; +} + +impl FfiType for MutPtr> +where + *mut T: FfiType, +{ + const C_DECL: &'static str = <*mut T as FfiType>::C_DECL; + type FfiType = *mut Pin; +} + +#[macro_export] +macro_rules! __internal_ptr_cffi { + ($pointee:ty, $c_pointee:literal) => { + $crate::inlinec::__internal_ptr_cffi!( + @impl, $pointee, $pointee, $c_pointee, + "", "" + ); + + // TODO: These implementations are necessary since we cannot currently have a recursive + // implementation for ConstPtr>, because we cannot express the resulting C_DECL + // (lack of const function in traits). We therefore unroll 2 level of pointers, since we + // don't really need more in practice. + $crate::inlinec::__internal_ptr_cffi!( + @impl, + $crate::inlinec::ConstPtr<$pointee>, + <$crate::inlinec::ConstPtr<$pointee> as $crate::inlinec::FfiType>::FfiType, + $c_pointee, + "CONST_TY_DECL(PTR_TY_DECL(", "))" + ); + $crate::inlinec::__internal_ptr_cffi!( + @impl, + $crate::inlinec::MutPtr<$pointee>, + <$crate::inlinec::MutPtr<$pointee> as $crate::inlinec::FfiType>::FfiType, + $c_pointee, + "PTR_TY_DECL(", ")" + ); + }; + (@impl, $pointee:ty, $ffi_pointee:ty, $c_pointee:literal, $decl_pre:literal, $decl_post:literal) => { + impl $crate::inlinec::FfiType for $crate::inlinec::ConstPtr<$pointee> { + const C_DECL: &'static str = $crate::misc::concatcp!( + "BUILTIN_TY_DECL(", $c_pointee, ", ", $decl_pre, "CONST_TY_DECL(PTR_TY_DECL(DECLARATOR))", $decl_post, ")" + ); + type FfiType = *const $ffi_pointee; + } + + impl $crate::inlinec::FfiType for $crate::inlinec::MutPtr<$pointee> { + const C_DECL: &'static str = $crate::misc::concatcp!( + "BUILTIN_TY_DECL(", $c_pointee, ", ", $decl_pre, "PTR_TY_DECL(DECLARATOR)", $decl_post, ")" + ); + type FfiType = *mut $ffi_pointee; + } + } +} +pub use crate::__internal_ptr_cffi; + +macro_rules! transparent_cffi { + ($ty:ty, $c_name:literal) => { + impl FfiType for $ty { + const C_DECL: &'static str = + $crate::misc::concatcp!("BUILTIN_TY_DECL(", $c_name, ", DECLARATOR)"); + type FfiType = $ty; + } + + impl FromFfi for $ty { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + x + } + } + + impl IntoFfi for $ty { + #[inline] + fn into_ffi(self) -> Self::FfiType { + self + } + } + + __internal_ptr_cffi!($ty, $c_name); + }; +} + +transparent_cffi!(u8, "uint8_t"); +transparent_cffi!(u16, "uint16_t"); +transparent_cffi!(u32, "uint32_t"); +transparent_cffi!(u64, "uint64_t"); +transparent_cffi!(usize, "size_t"); + +transparent_cffi!(i8, "int8_t"); +transparent_cffi!(i16, "int16_t"); +transparent_cffi!(i32, "int32_t"); +transparent_cffi!(i64, "int64_t"); +transparent_cffi!(isize, "ssize_t"); + +transparent_cffi!(bool, "_Bool"); + +// This is used for function returning void exclusively. We never pass a void parameter to a +// function. +impl FfiType for () { + const C_DECL: &'static str = "BUILTIN_TY_DECL(void, DECLARATOR)"; + type FfiType = (); +} + +impl FromFfi for () { + #[inline] + unsafe fn from_ffi(_: Self::FfiType) -> Self {} +} + +impl IntoFfi for () { + #[inline] + fn into_ffi(self) -> Self::FfiType {} +} + +// This is used for C void pointers exclusively. The usage is distinct from a function returning +// void, which is covered by the unit type (). +impl FfiType for c_void { + const C_DECL: &'static str = "BUILTIN_TY_DECL(void, DECLARATOR)"; + type FfiType = c_void; +} +// Only implement FromFfi/IntoFfi for pointers to c_void, never for c_void itself +__internal_ptr_cffi!(c_void, "void"); + +pub trait NullPtr { + fn null_mut() -> *mut Self; + #[inline] + fn null() -> *const Self { + Self::null_mut() + } +} + +impl NullPtr for T { + #[inline] + fn null_mut() -> *mut Self { + null_mut() + } +} + +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, + MutPtr: FfiType, +{ + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl IntoFfi for Option> +where + T: NullPtr, + MutPtr: FfiType + IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + MutPtr(match self { + None => ::null_mut(), + Some(p) => p.as_ptr(), + }) + .into_ffi() + } +} + +impl FromFfi for Option> +where + T: ?Sized, + MutPtr: FfiType + FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr: MutPtr = FromFfi::from_ffi(x); + let ptr = ptr.0; + if ptr.is_null() { + None + } else { + Some(NonNull::new(ptr).unwrap()) + } + } +} + +impl FfiType for NonNull +where + T: ?Sized, + Option>: FfiType, +{ + const C_DECL: &'static str = > as FfiType>::C_DECL; + type FfiType = > as FfiType>::FfiType; +} + +impl IntoFfi for NonNull +where + T: ?Sized, + Option>: IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + Some(self).into_ffi() + } +} + +impl FromFfi for NonNull +where + T: ?Sized, + Option>: FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let x = > as FromFfi>::from_ffi(x); + x.expect("NULL pointer was passed to NonNull") + } +} + +impl FfiType for Option<&CStr> { + const C_DECL: &'static str = <&'static c_char as FfiType>::C_DECL; + type FfiType = <&'static c_char as FfiType>::FfiType; +} + +impl IntoFfi for Option<&CStr> { + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + None => null(), + Some(s) => s.as_ptr(), + } + } +} + +impl FromFfi for Option<&CStr> { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + if x.is_null() { + None + } else { + Some(CStr::from_ptr(x)) + } + } +} + +impl<'a> FfiType for &'a CStr { + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl IntoFfi for &CStr { + #[inline] + fn into_ffi(self) -> Self::FfiType { + Some(self).into_ffi() + } +} + +impl FromFfi for &CStr { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + Option::<&CStr>::from_ffi(x) + .expect("NULL pointer was returned as a &CStr, use Option<&CStr> to allow that.") + } +} + +impl<'a> FfiType for &'a str { + const C_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; + type FfiType = <&'a CStr as FfiType>::FfiType; +} + +impl FromFfi for &str { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + <&CStr>::from_ffi(x) + .to_str() + .expect("Invalid UTF-8 content in C string") + } +} + +// 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_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; + type FfiType = <&'a CStr as FfiType>::FfiType; +} + +impl FromFfi for Option<&str> { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + Some( + >::from_ffi(x)? + .to_str() + .expect("Invalid UTF-8 content in C string"), + ) + } +} + +trait Pointer { + type Pointee; +} + +impl Pointer for *const T { + type Pointee = T; +} + +impl Pointer for *mut T { + type Pointee = T; +} + +#[repr(C)] +#[allow(private_bounds)] +pub struct FfiSlice +where + // SAFETY: Guarantees *const T has the same layout as a C pointer (i.e. it is a thin pointer, + // not a fat pointer) + Ptr: Pointer, +{ + // This layout guarantees minimum padding if natural alignment is followed since sizeof(*const + // T) >= sizeof(usize). It also matches the layout of a Rust slice, so convertions to and from + // it can be fully optimized out by the compiler. + data: Ptr, + len: usize, +} + +impl FfiType for ConstPtr<[u8]> { + const C_DECL: &'static str = "BUILTIN_TY_DECL(struct slice_const_u8, DECLARATOR)"; + type FfiType = FfiSlice<*const u8>; +} + +impl FfiType for MutPtr<[u8]> { + const C_DECL: &'static str = "BUILTIN_TY_DECL(struct slice_u8, DECLARATOR)"; + type FfiType = FfiSlice<*mut u8>; +} + +impl IntoFfi for ConstPtr<[T]> +where + ConstPtr<[T]>: FfiType>, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + let this = self.0; + Self::FfiType { + data: this as *const _, + len: this.len(), + } + } +} + +impl FromFfi for ConstPtr<[T]> +where + ConstPtr<[T]>: FfiType>, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + ConstPtr(core::ptr::slice_from_raw_parts(x.data, x.len)) + } +} + +impl IntoFfi for MutPtr<[T]> +where + MutPtr<[T]>: FfiType>, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + let this = self.0; + Self::FfiType { + data: this as *mut _, + len: this.len(), + } + } +} + +impl FromFfi for MutPtr<[T]> +where + MutPtr<[T]>: FfiType>, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + MutPtr(core::ptr::slice_from_raw_parts_mut(x.data, x.len)) + } +} + +impl FfiType for Result<(), c_int> { + const C_DECL: &'static str = "BUILTIN_TY_DECL(int, DECLARATOR)"; + type FfiType = c_int; +} + +impl IntoFfi for Result<(), c_int> { + #[inline] + fn into_ffi(self) -> Self::FfiType { + match self { + Ok(()) => 0, + Err(x) => x, + } + } +} + +impl FromFfi for Result<(), c_int> { + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + match x { + 0 => Ok(()), + x => Err(x), + } + } +} + +impl FfiType for Result<(), Infallible> { + const C_DECL: &'static str = "BUILTIN_TY_DECL(void, DECLARATOR)"; + type FfiType = (); +} + +impl FromFfi for Result<(), Infallible> { + #[inline] + unsafe fn from_ffi(_: Self::FfiType) -> Self { + Ok(()) + } +} + +pub struct Align(::Aligned) +where + Self: GetAligned; + +pub trait GetAligned { + type Aligned; +} + +macro_rules! make_getaligned { + ($($align:tt),*) => { + $( + const _:() = { + #[repr(align($align))] + pub struct Aligned; + impl GetAligned for Align<$align> { + type Aligned = Aligned; + } + }; + )* + } +} + +make_getaligned!( + 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, + 262144, 524288 +); + +pub trait Opaque { + #[inline] + unsafe fn try_new(init: F) -> Result + where + Self: Sized, + F: FnOnce(*mut Self) -> Result<(), E>, + { + let mut this = Self::new_uninit(); + init(this.as_mut_ptr()).map(|_| unsafe { this.assume_init() }) + } + + #[inline] + fn new_uninit() -> MaybeUninit + where + Self: Sized, + { + MaybeUninit::uninit() + } + + #[inline] + unsafe fn new_stack(init: F) -> Result + where + Self: Sized, + F: FnOnce(*mut Self) -> Result<(), E>, + { + let mut new = Self::new_uninit(); + let ptr: *mut Self = new.as_mut_ptr(); + // SAFETY: If the function succeeded, the contract means we can assume self is initialized. + init(ptr).map(|_| unsafe { new.assume_init() }) + } + + #[inline] + unsafe fn new_arc(init: F) -> Result, E> + where + Self: Sized, + F: FnOnce(*mut Self) -> Result<(), E>, + { + let mut arc = Arc::new_uninit(); + let ptr: *mut Self = Arc::get_mut(&mut arc).unwrap().as_mut_ptr(); + // SAFETY: If the function succeeded, the contract means we can assume self is initialized. + init(ptr).map(|_| unsafe { arc.assume_init() }) + } + + #[inline] + unsafe fn new_box(init: F) -> Result, E> + where + Self: Sized, + F: FnOnce(*mut Self) -> Result<(), E>, + { + let mut b = Box::new_uninit(); + let maybe: &mut MaybeUninit = &mut b; + let ptr: *mut Self = maybe.as_mut_ptr(); + // SAFETY: If the function succeeded, the contract means we can assume self is initialized. + init(ptr).map(|_| unsafe { b.assume_init() }) + } +} + +#[macro_export] +macro_rules! __internal_opaque_type { + ($vis:vis struct $name:ident, $c_name:literal, $c_header:literal) => { + // Model opaque types as recommended in the Rustonomicon: + // https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs + #[repr(C)] + $vis struct $name { + _data: [ + u8; + $crate::inlinec::c_constant!( + ("#include <", $c_header, ">"), + ("sizeof (", $c_name, ")"), + "1" + ) + ], + // Since we cannot make Opaque types aligned with a simple attribute + // (#[repr(align(my_macro!()))] is rejected since my_macro!() is not an integer + // literal), we add a zero-sized member that allows specifying the alignment as a + // generic const parameter. + _align: $crate::inlinec::Align<{ + $crate::inlinec::c_constant!( + ("#include <", $c_header, ">"), + ("_Alignof (", $c_name, ")"), + "1" + ) + }>, + _marker: + ::core::marker::PhantomData<(*mut u8, ::core::marker::PhantomPinned)>, + } + + // Double check that the we did not fumble the Rust struct layout somehow. + const _:() = { + const fn member_layout &B>(f: F) -> (usize, usize) { + ::core::mem::forget(f); + ( + ::core::mem::size_of::(), + ::core::mem::align_of::(), + ) + } + let (size, _): (usize, usize) = member_layout(|x: &$name| &x._data); + let (_, align): (usize, usize) = member_layout(|x: &$name| &x._align); + // Check alignment first as a wrong alignment will likely impact the overal size as + // well due to different padding. + assert!( + ::core::mem::align_of::<$name>() == align, + "Rust opaque type alignment differs from C type." + ); + assert!( + ::core::mem::size_of::<$name>() == size, + "Rust opaque type size differs from C type." + ); + }; + + $crate::inlinec::__internal_ptr_cffi!($name, $c_name); + + use $crate::inlinec::Opaque as _; + impl $crate::inlinec::Opaque for $name {} + + }; +} +// Since the macro is tagged with #[macro_export], it will be exposed in the crate namespace +// directly for public use. We then re-export it from here under its pretty name, so that it is +// effectively part of the pub API of the current module (and technically as part of the root +// namespace under its private name). +pub use crate::__internal_opaque_type as opaque_type; + +#[macro_export] +macro_rules! __internal_c_static_assert { + ($headers:literal, $expr:tt) => {{ + #[$crate::inlinec::cfunc] + fn constant_assert() { + $headers; + $crate::misc::concatcp!( + "_Static_assert(", + "(", + $expr, + "),", + stringify!("C static assert failed"), + ");" + ) + } + }}; +} +pub use crate::__internal_c_static_assert as c_static_assert; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c42c1e01aff591a608acf91aed2bebdaaf1c4d6d --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/lib.rs @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#![no_std] +#![feature(fundamental)] +#![feature(layout_for_ptr)] + +extern crate alloc; + +// Without that, proc macros that contains references to items such as ::lisakmod_macros::foo in +// the expand code will not work, as the current crate is named "crate" but not +// "lisakmod_macros". With the "extern crate", we introduce our name in our current scope. +// +// https://users.rust-lang.org/t/how-to-express-crate-path-in-procedural-macros/91274/17 +extern crate self as lisakmod_macros; + +pub mod inlinec; +pub mod misc; +pub mod private; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs new file mode 100644 index 0000000000000000000000000000000000000000..ac505dbb5318ea9cbe31f6c3b1410204439d7345 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/misc.rs @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +pub use lisakmod_macros_proc::concatcp; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs new file mode 100644 index 0000000000000000000000000000000000000000..5f9e78486d9a1d72421f45e96158c4bb3862d6e3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/misc.rs @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +pub const fn concat(slices: &[&'static str]) -> [u8; N] { + let mut out: [u8; N] = [0; N]; + let mut out_i: usize = 0; + + let nr_slices = slices.len(); + let mut slice_i = 0; + while slice_i < nr_slices { + let slice = slices[slice_i]; + slice_i += 1; + + let slice: &[u8] = slice.as_bytes(); + + let slice_len = slice.len(); + let mut curr_slice_i: usize = 0; + + while curr_slice_i < slice_len { + out[out_i] = slice[curr_slice_i]; + out_i += 1; + curr_slice_i += 1; + } + } + out +} + +pub const fn slice_to_array(slice: &[u8]) -> [u8; N] { + let mut arr = [0u8; N]; + let mut idx: usize = 0; + while idx < N { + arr[idx] = slice[idx]; + idx += 1; + } + arr +} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/mod.rs similarity index 50% rename from lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs rename to lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/mod.rs index b8116cb73876554560a85644dfacfe229ca71f69..c19387e1d5aeb6e6ce673ee1a97275050c2812bb 100644 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/src/private/mod.rs @@ -1,5 +1,2 @@ /* SPDX-License-Identifier: Apache-2.0 */ - -pub use const_format; - -pub mod inlinec; +pub mod misc; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/tests/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/tests/inlinec.rs new file mode 100644 index 0000000000000000000000000000000000000000..49ceeda597e71abd264d938433b841ac961982d2 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod-macros/tests/inlinec.rs @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +use lisakmod_macros::inlinec::c_constant; + +// #[cfunc] +// fn myfunc(x: u64, y: u64) -> u8 { +// "#include "; + +// r#" +// #ifdef FOOBAR +// if (x == 3) { +// return 1; +// } else { +// return y; +// } +// #endif +// "# +// } + +// #[cfunc] +// fn myfunc2(x: u64, y: u64) { +// "#include "; + +// r#" +// return; +// "# +// } + +// use core::ffi::CStr; +// #[cfunc] +// unsafe fn myfunc3<'a, 'b>(x: &'b CStr) -> &'a str +// where +// 'a: 'b, +// { +// "#include "; + +// r#" +// return; +// "# +// } + +// #[cfunc] +// fn myfunc4(x: *const *const u8) { +// // fn myfunc4(x: ConstPtr>) { +// "#include "; + +// r#" +// return; +// "# +// } + +#[test] +fn test_cfunc() { + // assert_eq!(myfunc(1, 2), 2); + + macro_rules! mymac { + ($header:literal, $ty:literal) => { + c_constant!(("#include <", $header, ">"), ("sizeof(", $ty, ")"), "8") + }; + } + assert_eq!(mymac!("linux/kobject.h", "int"), 8); +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..d62f138ed95a97ed43d52e02c7aa187ed3845355 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.lock @@ -0,0 +1,91 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "lisakmod" +version = "0.1.0" +dependencies = [ + "lisakmod_macros", +] + +[[package]] +name = "lisakmod_macros" +version = "0.1.0" +dependencies = [ + "lisakmod_macros_proc", +] + +[[package]] +name = "lisakmod_macros_proc" +version = "0.1.0" +dependencies = [ + "getrandom", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml index dccabab032fe645315a91c173eae45decb5736d1..030518f152fe038c654e4d316ecdd0db614fb73c 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/Cargo.toml @@ -1,15 +1,16 @@ +cargo-features = ["edition2024"] + [package] name = "lisakmod" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] crate-type = ["staticlib"] [dependencies] # hashbrown = "0.15" -lisakmodprocmacro = { path = "../proc_macros/lisakmodprocmacro" } -paste = "1.0" +lisakmod_macros = { path = "../lisakmod-macros" } [profile.release] panic = 'abort' diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/bindings.h b/lisa/_assets/kmodules/lisa/rust/lisakmod/bindings.h new file mode 100644 index 0000000000000000000000000000000000000000..86d80ee8afdaed1aa4ee7d6271f3f217f61b64d3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/bindings.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include "rust/lisakmod/cbindgen.h" +#include "generated/rust/rust_c_shims.h" + diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h index 0027b64af1a135da3f8ce6c101c240f78afeeb38..97fac2acd2a2eeae071a2d75b007121653e037bc 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/cbindgen.h @@ -7,5 +7,3 @@ extern uint64_t myc_callback(uint64_t x); -uint64_t do_rust_tests(void) ; - diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c b/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c index 4b07c98aea9c9db723e2bea99ea3c27cbcebee4c..ee2b8f15db50660c170676077587a9896d3b2fd1 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/runtime.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: GPL-2.0 */ #include "main.h" -#include "rust/lisakmod/cbindgen.h" +#include "rust/lisakmod/bindings.h" /* Basic glue between Rust runtime and kernel functions */ -/* C shims generated by the lisakmodprocmacro::inlinec module */ -#include "generated/rust/rust_c_shims.h" +/* C shims generated by the lisakmod_macros::inlinec module */ +#include "rust/lisakmod-macros/cffi.h" +#include "generated/rust/rust_c_shims.c" diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds b/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds index cda52d31561a0f9cffe9573ca9e1c68f179d8f42..05f89cdca8409c79840c2b3309a0724674b96609 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/rust.lds @@ -2,6 +2,6 @@ PROVIDE(__builtin_copysignq = 0); SECTIONS { - /* Remove binstore sections that we created in lisakmodprocmacro::inlinec Rust module */ + /* Remove binstore sections that we created in lisakmod_macros::inlinec Rust module */ /DISCARD/ : { *(.binstore.*) } } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs new file mode 100644 index 0000000000000000000000000000000000000000..c30a565206676c6d52337629f937e27754b9f40d --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/init.rs @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use core::ffi::c_int; + +use crate::runtime::printk::pr_err; + +pub fn module_main() -> impl Iterator { + gen { + // use crate::runtime::sysfs::{KObjType, KObject}; + + // let root = KObject::module_root(); + // let kobj_type = Arc::new(KObjType::new()); + // let kobject = KObject::new(kobj_type.clone()); + // let kobject2 = KObject::new(kobj_type.clone()); + // kobject.add(Some(&root), "foo"); + // kobject2.add(Some(&kobject), "bar"); + yield match crate::tests::init_tests() { + Err(x) => { + pr_err!("Lisa module Rust support validation failed: {x}"); + 0 + } + Ok(()) => 0, + }; + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs index 23ed47c850acdbb1be170fcf02354d73076cff2a..6c68998fda1e6c0541a5019f5420df491b0eadf8 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/inlinec.rs @@ -1,20 +1,18 @@ /* SPDX-License-Identifier: GPL-2.0 */ -pub use lisakmodprocmacro::cfunc; +pub use lisakmod_macros::inlinec::*; -macro_rules! get_c_macro { - ($header:expr, $macro:ident, $ty:ty) => {{ - ::paste::paste! { - // Emit the C function code that will be extracted from the Rust object file and then - // compiled as C. - #[::lisakmodprocmacro::cfunc] - #[allow(non_snake_case)] - fn [<__macro_getter_ $macro>]() -> $ty { - concat!("#include<", $header, ">"); - concat!("return (", stringify!($macro), ");") - } - [<__macro_getter_ $macro>]() +macro_rules! c_eval { + ($header:expr, $expr:literal, $ty:ty) => {{ + // Emit the C function code that will be extracted from the Rust object file and then + // compiled as C. + #[::lisakmod_macros::inlinec::cfunc] + #[allow(non_snake_case)] + fn snippet() -> $ty { + concat!("#include<", $header, ">"); + concat!("return (", $expr, ");") } + snippet() }}; } -pub(crate) use get_c_macro; +pub(crate) use c_eval; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs index 16dc8d94521e6ae46c54051df2661d9939af3e22..da009096ef9f7fc5fc264b78b6cbfc8b331cffa7 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/lib.rs @@ -1,10 +1,32 @@ /* SPDX-License-Identifier: GPL-2.0 */ #![no_std] #![no_builtins] +#![feature(gen_blocks)] extern crate alloc; +pub mod init; pub mod inlinec; pub mod prelude; pub mod runtime; pub mod tests; + +macro_rules! container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ + let ptr = $ptr; + let ptr: *const _ = (&*ptr); + let offset = core::mem::offset_of!($container, $member); + // SAFETY: Our contract is that c_kobj must be the member of a KObjectInner, so we can + // safely compute the pointer to the parent. + let container: *const $container = (ptr as *const $container).byte_sub(offset); + container + }}; +} +pub(crate) use container_of; + +#[allow(unused_macros)] +macro_rules! mut_container_of { + ($container:ty, $member:ident, $ptr:expr) => {{ $crate::container_of!($container, $member, $ptr) as *mut $container }}; +} +#[allow(unused_imports)] +pub(crate) use mut_container_of; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs index 55f82218dca6b70d73b1237a92c4172a79572fe9..f2bb322d184b4b870d6397cd8dbb627fb0c955b9 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/prelude.rs @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ -pub use crate::{inlinec::cfunc, runtime::kbox::KBox}; +pub use crate::{ + inlinec::{cexport, cfunc}, + runtime::kbox::KBox, +}; #[allow(unused_imports)] pub(crate) use crate::{ - inlinec::get_c_macro, + inlinec::c_eval, runtime::printk::{pr_err, pr_info}, }; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs index 289188c703d58f1fdeebf0513b62f56fc2d4124f..5c71287d6ca6251e7c5503b41534747f180c9d07 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/alloc.rs @@ -1,10 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0 */ use alloc::alloc::handle_alloc_error; -use core::alloc::{GlobalAlloc, Layout}; +use core::{ + alloc::{GlobalAlloc, Layout}, + ffi::c_void, +}; use crate::{ - inlinec::{cfunc, get_c_macro}, + inlinec::{c_eval, cfunc}, runtime::printk::pr_err, }; @@ -12,7 +15,7 @@ struct KernelAllocator; #[inline] fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { - let minalign = || get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + let minalign = || c_eval!("linux/slab.h", "ARCH_KMALLOC_MINALIGN", usize); let size = layout.size(); let align = layout.align(); @@ -24,7 +27,10 @@ fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { ptr } else { // Do not panic as this would create UB - pr_err!("Rust: cannot allocate memory with alignment > {minalign} and a size {size} that is not a power of two", minalign=minalign()); + pr_err!( + "Rust: cannot allocate memory with alignment > {minalign} and a size {size} that is not a power of two", + minalign = minalign() + ); core::ptr::null_mut() } } @@ -32,6 +38,9 @@ fn with_size *mut u8>(layout: Layout, f: F) -> *mut u8 { /// This function is guaranteed to return the pointer given by the kernel's kmalloc() without /// re-aligning it in any way. This makes it suitable to pass to kfree() without knowing the /// original layout. +/// +/// Note that any layout is admissible here, including if layout.size() == 0 unlike when going +/// through the GlobalAlloc API. #[inline] pub fn kmalloc(layout: Layout) -> *mut u8 { #[cfunc] @@ -46,12 +55,12 @@ pub fn kmalloc(layout: Layout) -> *mut u8 { #[inline] pub unsafe fn kfree(ptr: *mut T) { #[cfunc] - unsafe fn dealloc(ptr: *mut u8) { + unsafe fn dealloc(ptr: *mut c_void) { "#include "; "return kfree(ptr);" } - unsafe { dealloc(ptr as *mut u8) } + unsafe { dealloc(ptr as *mut c_void) } } unsafe impl GlobalAlloc for KernelAllocator { diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs index 7b630ca8e7d335110892050cdf7279592658afe6..9629ab1bfad67c309004cb3fe3765a594f61b58b 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/kbox.rs @@ -7,12 +7,16 @@ use core::{ convert::{AsMut, AsRef}, fmt, hash::{Hash, Hasher}, + mem::MaybeUninit, + mem::forget, ops::{Deref, DerefMut}, + pin::Pin, ptr, ptr::NonNull, }; use crate::runtime::alloc::{kfree, kmalloc}; +use lisakmod_macros::inlinec::{FfiType, FromFfi, IntoFfi, MutPtr, Opaque}; pub struct KBox { ptr: NonNull, @@ -21,34 +25,106 @@ pub struct KBox { impl KBox { #[inline] pub fn new(x: T) -> Self + where + T: Sized, + { + let mut new = Self::new_uninit(); + let maybe: &mut MaybeUninit = &mut new; + maybe.write(x); + // SAFETY: The memory is now initialize. + unsafe { new.assume_init() } + } + + #[inline] + pub fn new_uninit() -> KBox> where T: Sized, { let layout = Layout::new::(); + Self::from_layout(layout) + } + + #[inline] + fn from_layout(layout: Layout) -> KBox> + where + T: Sized, + { let ptr = kmalloc(layout); - let ptr = ptr::NonNull::new(ptr as *mut T).expect("Allocation failed"); - unsafe { - ptr::write(ptr.as_ptr(), x); - } + let ptr = ptr::NonNull::new(ptr as *mut MaybeUninit).expect("Allocation failed"); + // SAFETY: MaybeUninit does not mandate any bit pattern for its data, so it's safe to + // construct it without any initialization (that's the whole point). KBox { ptr } } #[inline] - pub fn from_ptr(ptr: NonNull) -> Self { + pub fn as_ptr(&self) -> *const T { + self.ptr.as_ptr() + } + + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut T { + self.ptr.as_ptr() + } + + #[inline] + pub unsafe fn from_ptr(ptr: NonNull) -> Self { KBox { ptr } } #[inline] - pub fn as_ptr(&self) -> NonNull { - self.ptr + fn into_ptr(mut self) -> *mut T { + let ptr = self.as_mut_ptr(); + // SAFETY: Do not free memory here. + forget(self); + ptr + } + + #[inline] + pub fn into_pin(self) -> Pin { + // It's not possible to move or replace the insides of a `Pin>` + // when `T: !Unpin`, so it's safe to pin it directly without any + // additional requirements. + // + // We also satisfy the restriction stated in core::pin doc: + // > Pin requires that implementations of Deref and DerefMut on Ptr return a pointer + // > to the pinned data directly and do not move out of the self parameter during their + // > implementation of DerefMut::deref_mut. It is unsound for unsafe code to wrap pointer + // > types with such “malicious” implementations of Deref; see Pin::new_unchecked for + // > details. + // https://doc.rust-lang.org/std/pin/index.html#interaction-between-deref-and-pinptr + // + // https://doc.rust-lang.org/std/pin/struct.Pin.html#method.new_unchecked + unsafe { Pin::new_unchecked(self) } + } + + #[inline] + pub fn pin(x: T) -> Pin + where + T: Sized, + { + KBox::into_pin(KBox::new(x)) + } +} + +impl KBox> { + #[inline] + pub unsafe fn assume_init(self) -> KBox { + let ptr: *const MaybeUninit = self.into_ptr(); + // SAFETY: It is sound to cast *const MaybeUninit to *const T since MaybeUninit is + // #[repr(transparent)] + KBox { + ptr: unsafe { NonNull::new_unchecked(ptr as *mut T) }, + } } } impl Drop for KBox { #[inline] fn drop(&mut self) { + // SAFETY: KBox::ptr always points at an initialized and owned location. unsafe { self.ptr.drop_in_place(); + // Allocation was done with [kmalloc] so we can free with [kfree] kfree(self.ptr.as_ptr()); } } @@ -61,14 +137,9 @@ impl From for KBox { } } -impl From> for KBox { - #[inline] - fn from(t: NonNull) -> Self { - Self::from_ptr(t) - } -} - +// SAFETY: KBox owns the T, so it inherits T: Send unsafe impl Send for KBox {} +// SAFETY: KBox owns the T, so it inherits T: Sync unsafe impl Sync for KBox {} impl Deref for KBox { @@ -76,6 +147,7 @@ impl Deref for KBox { #[inline] fn deref(&self) -> &Self::Target { + // SAFETY: self.ptr is guaranteed to be valid and initialized unsafe { self.ptr.as_ref() } } } @@ -83,6 +155,7 @@ impl Deref for KBox { impl DerefMut for KBox { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: self.ptr is guaranteed to be valid and initialized unsafe { self.ptr.as_mut() } } } @@ -182,3 +255,58 @@ impl fmt::Pointer for KBox { } impl Unpin for KBox {} + +impl FfiType for KBox +where + T: ?Sized, + NonNull: FfiType, +{ + const C_DECL: &'static str = as FfiType>::C_DECL; + type FfiType = as FfiType>::FfiType; +} + +impl FromFfi for KBox +where + T: ?Sized, + MutPtr: FromFfi, +{ + #[inline] + unsafe fn from_ffi(x: Self::FfiType) -> Self { + let ptr: NonNull = unsafe { FromFfi::from_ffi(x) }; + // SAFETY: The calling code promises that ptr is valid and initialized. + unsafe { Self::from_ptr(ptr) } + } +} + +impl IntoFfi for KBox +where + T: ?Sized, + MutPtr: IntoFfi, +{ + #[inline] + fn into_ffi(self) -> Self::FfiType { + let ptr: *mut T = self.into_ptr(); + ptr.into_ffi() + } +} + +pub trait OpaqueExt: Opaque { + unsafe fn new_kbox(init: F) -> Result, E> + where + F: FnOnce(*mut Self) -> Result<(), E>; +} + +impl OpaqueExt for T +where + T: Opaque, +{ + unsafe fn new_kbox(init: F) -> Result, E> + where + F: FnOnce(*mut Self) -> Result<(), E>, + { + let mut b: KBox> = KBox::new_uninit(); + let ptr: *mut T = (*b).as_mut_ptr(); + // SAFETY: If init() succeeded, the value is now initialized. + init(ptr).map(|_| unsafe { b.assume_init() }) + } +} 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 9c5f8075110579eaf03ac62c834bb99445fcd26d..afc0b7a3eda2c3db9e4eaa44a1eb0b243e3a5c54 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/mod.rs @@ -2,5 +2,8 @@ pub mod alloc; pub mod kbox; +pub mod module; pub mod panic; pub mod printk; +pub mod sync; +pub mod sysfs; diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/module.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/module.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe1650c3d0bd9ad19e069fbdfc194f136703eab3 --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/module.rs @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::boxed::Box; +use core::ffi::c_int; + +use crate::init::module_main; +use crate::prelude::*; +use crate::runtime::sync::{Lock, new_static_mutex}; + +new_static_mutex!( + MAIN_ITERATOR, + Option + Send>>, + None +); + +#[cexport(link_name("rust_mod_init"))] +pub fn rust_mod_init() -> c_int { + let mut iterator = Box::new(module_main()); + let ret = iterator.next().expect("Main iterator did not yield once"); + if ret == 0 { + *MAIN_ITERATOR.lock() = Some(iterator); + 0 + } else { + assert_eq!( + iterator.next(), + None, + "Main iterator yielded more than once" + ); + ret + } +} + +#[cexport(link_name("rust_mod_exit"))] +pub fn rust_mod_exit() { + let mut iterator = MAIN_ITERATOR.lock(); + let iterator = iterator + .as_mut() + .expect("rust_mod_init() was not called before rust_mod_exit()"); + assert_eq!( + iterator.next(), + None, + "Main iterator yielded more than once" + ); +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs index 9eaeb012b7ca8912a08451117161d51003c7e30b..3c6fbb305089ef2daffce04e012c6ea4125c5bf2 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/panic.rs @@ -52,10 +52,10 @@ impl fmt::Write for SliceWriter<'_> { #[allow(dead_code)] fn _panic(info: &core::panic::PanicInfo) -> ! { #[cfunc] - unsafe fn panic(msg: *const u8, len: usize) { + fn panic<'a>(msg: &'a [u8]) { r#" - if (msg && len) { - panic("Rust panic: %.*s", (int)len, msg); + if (msg.data && msg.len) { + panic("Rust panic: %.*s", (int)msg.len, msg.data); } else { panic("Rust panic with no message"); } @@ -74,7 +74,7 @@ fn _panic(info: &core::panic::PanicInfo) -> ! { } } }; - unsafe { panic(out.as_ptr(), out.len()) }; + panic(out); #[allow(clippy::empty_loop)] loop {} 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 924343727a08e66548ad4482013d12f632dd7fc5..f8dea14be82054ace1bb34c1d1d68070049de004 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/printk.rs @@ -44,27 +44,27 @@ impl core::fmt::Write for __DmesgWriter { self.state = DmesgWriterState::Cont; #[cfunc] - unsafe fn printk(level: u8, msg: *const u8, len: usize) { + fn printk<'a>(level: u8, msg: &[u8]) { "#include "; r#" - #define HANDLE(level, f) case level: f("%.*s", (int)len, msg); break; - switch (level) { - HANDLE(0, pr_emerg); - HANDLE(1, pr_alert); - HANDLE(2, pr_crit); - HANDLE(3, pr_err); - HANDLE(4, pr_warn); - HANDLE(5, pr_notice); - HANDLE(6, pr_info); - HANDLE(7, pr_debug); - HANDLE(8, pr_cont); - } - #undef HANDLE + #define HANDLE(level, f) case level: f("%.*s", (int)msg.len, msg.data); break; + switch (level) { + HANDLE(0, pr_emerg); + HANDLE(1, pr_alert); + HANDLE(2, pr_crit); + HANDLE(3, pr_err); + HANDLE(4, pr_warn); + HANDLE(5, pr_notice); + HANDLE(6, pr_info); + HANDLE(7, pr_debug); + HANDLE(8, pr_cont); + } + #undef HANDLE "# } - unsafe { printk(level as u8, s.as_ptr(), s.len()) } + printk(level as u8, s.as_bytes()); Ok(()) } } diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs new file mode 100644 index 0000000000000000000000000000000000000000..0eb23c6a4247eafdb912e96dc7dbbf721c75cf5b --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sync.rs @@ -0,0 +1,514 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::boxed::Box; +use core::{ + cell::{Cell, UnsafeCell}, + ffi::c_void, + marker::PhantomData, + ops::{Deref, DerefMut}, + pin::Pin, +}; + +use crate::{ + inlinec::{cfunc, opaque_type}, + runtime::kbox::KBox, +}; + +pub trait LockGuard +where + Self: Deref, +{ +} + +pub trait Lock +where + Self: Sync, +{ + type Guard<'a>: LockGuard + where + Self: 'a; + fn lock(&self) -> Self::Guard<'_>; + + #[inline] + fn with_lock U>(&self, f: F) -> U { + f(self.lock().deref()) + } +} + +#[allow(private_bounds)] +pub trait LockMut: for<'a> _LockMut<'a, T> {} + +// TODO: We need to share the 'a lifetime between the Guard<'a> type and Self: 'a. Unfortunately, +// there seems to be no direct way of doing that since a for<> lifetime only affects what comes +// immediately after it. As a workaround, we can just hide that in a trait, and then use +// for<'a>_LockMut<'a, T> in the user-exposed trait. +trait _LockMut<'a, T> +where + >::Guard<'a>: DerefMut, + Self: 'a + Lock, +{ +} + +opaque_type!(struct CSpinLock, "spinlock_t", "linux/spinlock.h"); + +pub struct SpinLock { + data: UnsafeCell, + // The Rust For Linux binding pins the spinlock binding, so do the same here to avoid any + // problems. + c_lock: Pin>>, +} + +impl SpinLock { + #[inline] + pub fn new(x: T) -> Self { + #[cfunc] + fn spinlock_alloc() -> Pin>> { + r#" + #include + #include + "#; + + r#" + spinlock_t *lock = kzalloc(sizeof(spinlock_t), GFP_KERNEL); + if (lock) { + spin_lock_init(lock); + } + return lock; + "# + } + let c_lock = spinlock_alloc(); + SpinLock { + c_lock, + data: x.into(), + } + } + + #[inline] + pub fn is_locked(&self) -> bool { + #[cfunc] + fn lock_is_locked(lock: &UnsafeCell) -> bool { + "#include "; + + r#" + return spin_is_locked(lock); + "# + } + lock_is_locked(&self.c_lock) + } +} + +impl Drop for SpinLock { + #[inline] + fn drop(&mut self) { + if self.is_locked() { + panic!("Attempted to drop a currently-locked spinlock"); + } + } +} + +pub struct SpinLockGuard<'a, T> { + lock: &'a SpinLock, + flags: u64, +} + +impl SpinLockGuard<'_, T> { + #[inline] + #[allow(clippy::mut_from_ref)] + fn get_mut(&self) -> &mut T { + let ptr = self.lock.data.get(); + // SAFETY: If SpinLockGuard exists, then the protected T data cannot currently be used + // by any other piece of code since we acquired the lock successfully. Since T is behind an + // UnsafeCell, it is sound to access the data as long as there is no race. + // + // Note that this only stands because the kernel spinlocks are not recursive. Such + // recursive call would be allowed by the borrow checker, but will end up in a deadlock so + // we will never _actually_ end up creating two &mut T pointing at the same piece of data. + unsafe { &mut *ptr } + } +} + +impl Deref for SpinLockGuard<'_, T> { + type Target = T; + #[inline] + fn deref(&self) -> &T { + self.get_mut() + } +} + +impl DerefMut for SpinLockGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +impl Drop for SpinLockGuard<'_, T> { + #[inline] + fn drop(&mut self) { + #[cfunc] + fn spinlock_unlock(lock: &UnsafeCell, flags: u64) { + "#include "; + + r#" + spin_unlock_irqrestore(lock, flags); + "# + } + spinlock_unlock(&self.lock.c_lock, self.flags); + } +} + +impl LockGuard for SpinLockGuard<'_, T> {} + +unsafe impl Sync for SpinLock {} +impl Lock for SpinLock { + type Guard<'a> + = SpinLockGuard<'a, T> + where + Self: 'a; + + #[inline] + fn lock(&self) -> Self::Guard<'_> { + #[cfunc] + fn spinlock_lock(lock: &UnsafeCell) -> u64 { + "#include "; + + r#" + unsigned long flags; + spin_lock_irqsave(lock, flags); + return (uint64_t)flags; + "# + } + let flags = spinlock_lock(&self.c_lock); + + SpinLockGuard { lock: self, flags } + } +} + +impl<'a, T: 'a + Send> _LockMut<'a, T> for SpinLock {} + +opaque_type!(pub struct CMutex, "struct mutex", "linux/mutex.h"); + +enum AllocatedCMutex { + KBox(Pin>>), + // FIXME: introduce #[cstatic] to allow defining a static global with C code, the same way we + // have #[cfunc]. This would be restricted to types that are FfiType + IntoFfi + + // FromFfi + + // Mutexes in static variables have to be defined in C code using the DEFINE_MUTEX() macro. + // Unfortunately, that means we can't get their address directly in Rust, as that would require + // having an equivalent of #[cfunc] for other values than functions, which would be doable but + // extra work. Instead, we can just make a C function that declares a static mutex inside it + // and returns its address. + Static(fn() -> Pin<&'static UnsafeCell>), +} + +impl AllocatedCMutex { + #[inline] + fn as_pin_ref(&self) -> Pin<&UnsafeCell> { + match self { + AllocatedCMutex::KBox(c_mutex) => c_mutex.as_ref(), + AllocatedCMutex::Static(f) => f(), + } + } +} + +macro_rules! new_static_mutex { + ($vis:vis $name:ident, $ty:ty, $data:expr) => { + $vis static $name: $crate::runtime::sync::Mutex<$ty> = + $crate::runtime::sync::Mutex::__internal_from_ref($data, { + #[$crate::inlinec::cfunc] + fn get_static_mutex() -> ::core::pin::Pin< + &'static ::core::cell::UnsafeCell<$crate::runtime::sync::CMutex>, + > { + "#include "; + + r#" + static DEFINE_MUTEX(mutex); + return &mutex; + "# + } + get_static_mutex + }); + }; +} +pub(crate) use new_static_mutex; + +pub struct Mutex { + data: UnsafeCell, + // struct mutex contains a list_head, so it must be pinned. + c_mutex: AllocatedCMutex, +} + +impl Mutex { + #[inline] + pub fn new(x: T) -> Self { + #[cfunc] + fn mutex_alloc() -> Pin>> { + r#" + #include + #include + "#; + + r#" + struct mutex *mutex = kzalloc(sizeof(struct mutex), GFP_KERNEL); + if (mutex) { + mutex_init(mutex); + } + return mutex; + "# + } + let c_mutex = mutex_alloc(); + Mutex { + c_mutex: AllocatedCMutex::KBox(c_mutex), + data: x.into(), + } + } + + #[inline] + pub const fn __internal_from_ref(x: T, f: fn() -> Pin<&'static UnsafeCell>) -> Self { + Mutex { + c_mutex: AllocatedCMutex::Static(f), + data: UnsafeCell::new(x), + } + } + + #[inline] + pub fn is_locked(&self) -> bool { + #[cfunc] + fn mutex_is_locked(lock: &UnsafeCell) -> bool { + "#include "; + + r#" + return mutex_is_locked(lock); + "# + } + mutex_is_locked(&self.c_mutex.as_pin_ref()) + } +} + +impl Drop for Mutex { + #[inline] + fn drop(&mut self) { + if self.is_locked() { + panic!("Attempted to drop a currently-locked mutex"); + } + } +} + +pub struct MutexGuard<'a, T> { + mutex: &'a Mutex, +} + +impl MutexGuard<'_, T> { + #[inline] + #[allow(clippy::mut_from_ref)] + fn get_mut(&self) -> &mut T { + let ptr = self.mutex.data.get(); + // SAFETY: If MutexGuard exists, then the protected T data cannot currently be used + // by any other piece of code since we acquired the lock successfully. Since T is behind an + // UnsafeCell, it is sound to access the data as long as there is no race. + // + // Note that this only stands because the kernel mutex are not recursive. Such + // recursive call would be allowed by the borrow checker, but will end up in a deadlock so + // we will never _actually_ end up creating two &mut T pointing at the same piece of data. + unsafe { &mut *ptr } + } +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + #[inline] + fn deref(&self) -> &T { + self.get_mut() + } +} + +impl DerefMut for MutexGuard<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +impl Drop for MutexGuard<'_, T> { + #[inline] + fn drop(&mut self) { + #[cfunc] + fn mutex_unlock(mutex: &UnsafeCell) { + "#include "; + + r#" + mutex_unlock(mutex); + "# + } + mutex_unlock(&self.mutex.c_mutex.as_pin_ref()); + } +} + +impl LockGuard for MutexGuard<'_, T> {} + +unsafe impl Sync for Mutex {} +impl Lock for Mutex { + type Guard<'a> + = MutexGuard<'a, T> + where + Self: 'a; + + #[inline] + fn lock(&self) -> Self::Guard<'_> { + #[cfunc] + fn mutex_lock(mutex: &UnsafeCell) { + "#include "; + + r#" + return mutex_lock(mutex); + "# + } + mutex_lock(&self.c_mutex.as_pin_ref()); + + MutexGuard { mutex: self } + } +} + +impl<'a, T: 'a + Send> _LockMut<'a, T> for Mutex {} + +pub struct Rcu { + // This pointer is actually a Box in disguise obtained with Box::into_raw(). We keep it as a + // raw pointer so that we can use C's rcu_assign_pointer() on it. + // + // Use a level of indirection so that Rcu does not need to dish out references to inside + // itself. This avoids the need for pinning it, making it much more ergonomic. Since an Rcu is + // typically mostly read and not often modified, the box allocation is not unreasonable. + data: Cell<*const T>, + + // Mutex used to protect the writers. + writer_mutex: Mutex<()>, +} + +impl Rcu { + pub fn new(data: T) -> Self { + Rcu { + data: Cell::new(Box::into_raw(Box::new(data))), + writer_mutex: Mutex::new(()), + } + } + + pub fn update(&self, data: T) { + #[cfunc] + unsafe fn rcu_assign(rcu: *mut *const c_void, new_ptr: *const c_void) { + "#include "; + + r#" + rcu_assign_pointer((*rcu), new_ptr); + "# + } + + #[cfunc] + fn rcu_synchronize() { + "#include "; + + r#" + synchronize_rcu(); + "# + } + + // Allocate the data before we take the writer lock to avoid taking it for longer than + // necessary. + let new_data = Box::into_raw(Box::new(data)); + + let writer_guard = self.writer_mutex.lock(); + + // Get the old pointer before we overwrite it. It is safe to take it without + // rcu_dereference() as we are holding the lock. This is equivalent to using + // rcu_dereference_protected(). + let old_data: *const T = self.data.get(); + + // SAFETY: It is safe to modify self.data here as: + // * We locked self.writer_mutex, so no-one else is attempting to modify it. + // * It is in a Cell<>, so interior mutability is allowed. + // * The update is atomically done by rcu_assign_pointer(), so no reader will see a + // torn value. + unsafe { + rcu_assign( + self.data.as_ptr() as *mut *const c_void, + new_data as *const c_void, + ); + } + + // Once we used rcu_assign_pointer(), we can drop the writer lock safely, wait for the + // grace period and drop the old data. + drop(writer_guard); + + rcu_synchronize(); + + // SAFETY: The Box is not in use anymore by anyone else after the call to + // synchronize_rcu(), so we can safely re-materialize it to drop it. + drop(unsafe { Box::from_raw(old_data as *mut T) }); + } +} + +pub struct RcuGuard<'a, T> { + data: *const T, + _phantom: PhantomData<&'a T>, +} + +impl LockGuard for RcuGuard<'_, T> {} + +impl Deref for RcuGuard<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: we are guaranteed that the pointer we obtained will be valid for the lifetime of + // the guard. + unsafe { self.data.as_ref().unwrap() } + } +} + +impl Drop for RcuGuard<'_, T> { + fn drop(&mut self) { + #[cfunc] + fn rcu_unlock() { + "#include "; + + r#" + rcu_read_unlock(); + "# + } + rcu_unlock(); + } +} + +unsafe impl Sync for Rcu {} +impl Lock for Rcu { + type Guard<'a> + = RcuGuard<'a, T> + where + Self: 'a; + + #[inline] + fn lock(&self) -> Self::Guard<'_> { + #[cfunc] + fn rcu_lock(ptr: *const c_void) -> *const c_void { + "#include "; + + r#" + rcu_read_lock(); + return rcu_dereference(ptr); + "# + } + // SAFETY: we guarantee that for the pointer we pass to RcuGuard<'a, T> will be valid for + // the lifetime 'a of the guard, sine the RcuGuard::drop() runs rcu_read_unlock() and we + // wait for synchronize_rcu() before dropping self.data + let data = { + let in_ptr = self.data.get() as *const c_void; + let out_ptr = rcu_lock(in_ptr); + assert_eq!(in_ptr, out_ptr); + out_ptr as *const T + }; + RcuGuard { + data, + _phantom: PhantomData, + } + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs new file mode 100644 index 0000000000000000000000000000000000000000..65d3dec753a7b6d183c87d7d1d87094a802a37cb --- /dev/null +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/runtime/sysfs.rs @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +use alloc::{boxed::Box, sync::Arc}; +use core::{ + cell::UnsafeCell, + convert::Infallible, + ffi::{c_int, c_uint, c_void}, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, +}; + +use crate::{ + inlinec::{c_eval, cexport, cfunc, opaque_type}, + {container_of, mut_container_of}, +}; + +opaque_type!(struct CKObj, "struct kobject", "linux/kobject.h"); +opaque_type!(struct CKObjType, "struct kobj_type", "linux/kobject.h"); + +// SAFETY: CKObjType is a plain-old-data struct, there is nothing in there that can't be shared +// between threads. +unsafe impl Send for CKObjType {} +unsafe impl Sync for CKObjType {} + +pub struct KObjType { + c_kobj_type: CKObjType, +} + +impl Default for KObjType { + fn default() -> Self { + Self::new() + } +} + +// This type alias is used to ensure that all the places that need to create an allocated +// KObjectInner and the ones that re-materalize one from a raw pointer agree on what exact type is +// used. +type AllocatedKObjectInner = Pin>; + +impl KObjType { + pub fn new() -> KObjType { + #[cexport] + fn release(this: &mut CKObj) { + // SAFETY: This assumes the CKObj is always only located inside a KObjectInner. It also + // assumes nothing else has a &mut KObjectInner over it, otherwise we would end up + // aliasing a mutable reference which is UB. + let inner: *mut KObjectInner = unsafe { mut_container_of!(KObjectInner, c_kobj, this) }; + + // SAFETY: We built this KObjectInner in a Box<> and used Box::into_raw(). Here we know + // that refcount == 0, so no-one else has a mutable reference to KObjectInner anymore, + // so we can rematerialize the Box and free the lot. + let b: AllocatedKObjectInner = Box::into_pin(unsafe { Box::from_raw(inner) }); + let kobj_type: *const KObjType = b.kobj_type(); + drop(b); + + // SAFETY: We destroyed KObjectInner, so now we can decrement the Arc refcount. + unsafe { + Arc::decrement_strong_count(kobj_type); + } + } + + #[cfunc] + fn init_kobj_type( + kobj_type: *mut CKObjType, + release: *const c_void, + ) -> Result<(), Infallible> { + r#" + #include + #include + "#; + + r#" + memset(kobj_type, 0, sizeof(*kobj_type)); + kobj_type->release = release; + "# + } + + let release_f: unsafe extern "C" fn(*mut CKObj) = release; + let init = |this| init_kobj_type(this, release_f as *const c_void); + let c_kobj_type = unsafe { CKObjType::new_stack(init) }.unwrap(); + KObjType { c_kobj_type } + } +} + +#[repr(transparent)] +struct KObjectInner { + c_kobj: UnsafeCell, + // CKObj contains a pointer to CKObjType, so we reflect that dependency here. + _phantom: PhantomData, +} + +// SAFETY: KObjectInner is expected to be shared between multiple threads, as is the underlying +// struct kobject. Not all of struct kobject is thread-safe, but the API we expose should. +unsafe impl Send for KObjectInner {} +unsafe impl Sync for KObjectInner {} + +const _: () = { + // This is relied on in order to be able transmute a CKObj pointer to a KObjectInner pointer so + // we can manipulate foreign kobjects, such as the sysfs root folder for the module in + // /sys/module/lisa/ + // + // If that assertion is not true anymore because we need to store metadata in KObjectInner, we + // would need to add a level of indirection. + assert!(core::mem::size_of::() == core::mem::size_of::()); +}; + +impl KObjectInner { + pub fn new(kobj_type: Arc) -> AllocatedKObjectInner { + #[cfunc] + unsafe fn kobj_init(kobj: *mut CKObj, kobj_type: &CKObjType) -> Result<(), Infallible> { + r#" + #include + #include + "#; + + r#" + // memset() is necessary here, otherwise kobj->name stays uninitialized and that leads + // to crashes when calling kobject_add() since it tries to free the previous name. + memset(kobj, 0, sizeof(*kobj)); + kobject_init(kobj, kobj_type); + "# + } + // Increase refcount since we are going to keep a pointer to KObjType. We decrement it + // later when we don't need it anymore. + core::mem::forget(kobj_type.clone()); + + // Initialize the CKObj in-place in the heap. This is best since: + // * In case kobject_init() expects the struct kobject to be pinned and never move again. + // * The API documentation states that in the future, initialization on the stack might + // become forbidden. So make sure we don't do that. + let mut new: Box> = Box::new_uninit(); + let new_ptr: *mut Self = new.as_mut_ptr(); + // SAFETY: new_ptr is valid + unsafe { + kobj_init((*new_ptr).c_kobj.get_mut(), &kobj_type.c_kobj_type).unwrap(); + (&raw mut (*new_ptr)._phantom).write(PhantomData); + } + + // SAFETY: We initialized succesfully all the members. + Box::into_pin(unsafe { new.assume_init() }) + } + + fn from_c_kobj(c_kobj: *mut CKObj) -> *mut KObjectInner { + // SAFETY: it is safe to do that since we check that KObjectInner is exactly the same size + // as CKObj and has repr(transparent) + c_kobj as *mut KObjectInner + } + + fn refcount(&self) -> u64 { + #[cfunc] + fn kobj_refcount(kobj: *const CKObj) -> c_uint { + r#" + #include + "#; + + r#" + return kref_read(&kobj->kref); + "# + } + kobj_refcount(self.c_kobj.get()).into() + } + + unsafe fn update_refcount(&self, increase: bool) { + #[cfunc] + fn kobj_update_refcount(kobj: *mut CKObj, increase: bool) -> bool { + r#" + #include + "#; + + r#" + if (increase) { + return kobject_get(kobj) != NULL; + } else { + kobject_put(kobj); + return 1; + } + "# + } + let ok = kobj_update_refcount(self.c_kobj.get(), increase); + if !ok { + panic!("Could not update KObjectInner refcount as it is undergoing destruction.") + } + } + + #[inline] + fn incref(&self) { + // SAFETY: increasing the refcount is always safe, since worst case is we leak memory. + unsafe { self.update_refcount(true) } + } + + #[inline] + unsafe fn decref(&self) { + unsafe { self.update_refcount(false) } + } + + fn kobj_type(&self) -> &KObjType { + #[cfunc] + fn kobj_kobj_type(kobj: *const CKObj) -> *const CKObjType { + r#" + #include + "#; + + r#" + return kobj->ktype; + "# + } + let c_kobj_type = kobj_kobj_type(self.c_kobj.get()); + // SAFETY: All the CKObjType we manipulate are part of a KObjType, so it is safe to cast + // back. + unsafe { + container_of!(KObjType, c_kobj_type, c_kobj_type) + .as_ref() + .unwrap() + } + } +} + +impl Drop for KObjectInner { + fn drop(&mut self) { + let refcount = self.refcount(); + if refcount != 0 { + panic!("Tried to drop a KObjType with non zero refcount: {refcount}") + } + } +} + +pub struct KObject { + // Use a pointer, so Rust does not make any assumption on the validity of the pointee. This + // simplifies the drop flow. + // + // Morally, this is a Pin<*const KObjectInner> since the data behind it is pinned (it's coming + // from an AllocatedKObjectInner and re-materalized as such when de-allocating). However, + // Pin requires Ptr: Deref for anything interesting so we can't actually use it here. + inner: *const KObjectInner, +} + +// SAFETY: KObject is just a reference to a KObjectInner, so it is safe to pass it around threads. +unsafe impl Send for KObject {} + +impl KObject { + #[inline] + pub fn new(kobj_type: Arc) -> KObject { + let inner = KObjectInner::new(kobj_type); + // SAFETY: Nothing in the public KObject API allows moving out the KObjectInner pointed at, + // since that KObjectInner could be shared by any number of KObject instances, just like + // ArcInner is shared among multiple Arc. We therefore can guarantee that KObjectInner will + // stay exactly where it is until it is garbage-collected using kobject_put() somewhere in + // our code or kernel code. + let inner = unsafe { Pin::into_inner_unchecked(inner) }; + KObject { + inner: Box::into_raw(inner), + } + } + + #[inline] + pub fn module_root() -> KObject { + let c_kobj = c_eval!("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. + let inner = unsafe { inner.as_ref().unwrap() }; + KObject::from_inner(inner) + } + + fn from_inner(inner: &KObjectInner) -> Self { + inner.incref(); + KObject { inner } + } + + fn inner(&self) -> &KObjectInner { + // SAFETY: Since we hold a kref reference, we know we are pointing at valid memory. + unsafe { self.inner.as_ref().unwrap() } + } + + fn c_kobj(&self) -> *mut CKObj { + self.inner().c_kobj.get() + } + + // FIXME: We need to prevent the following sequence: + // a=new() + // b=new() + // a.add(parent=Some(a), name=bar) + // b.add(parent=None, name=foo) + // + // This is because we can apparently only add children under a parent that has been added + // already, we cannot build the hierarchy "virtually" before attaching it via a top-level add. + pub fn add<'a, 'parent, 'name>(&'a self, parent: Option<&'parent Self>, name: &'name str) + where + 'parent: 'a, + { + #[cfunc] + unsafe fn kobj_add(kobj: *mut CKObj, parent: *mut CKObj, name: &[u8]) -> Result<(), c_int> { + r#" + #include + #include + "#; + + r#" + return kobject_add(kobj, parent, "%.*s", (int)name.len, name.data); + "# + } + let parent: *mut CKObj = match parent { + Some(parent) => parent.c_kobj(), + None => core::ptr::null_mut(), + }; + unsafe { kobj_add(self.c_kobj(), parent, name.as_bytes()) } + .expect("Failed to call kobject_add()"); + } +} + +impl Drop for KObject { + fn drop(&mut self) { + // SAFETY: We increased the refcount when created, so we decrease it when dropped. The + // release implementation of KObjType will take care of freeing the memory if needed. + // Also, since KObject::inner is a pointer, it is ok if what it points to becomes garbage + // during decref(), it would only be an issue if we had a reference instead of a pointer. + unsafe { + self.inner().decref(); + } + } +} + +impl Clone for KObject { + fn clone(&self) -> Self { + // SAFETY: self.inner is always valid as long as a KObject points to it. + let inner = unsafe { self.inner.as_ref().unwrap() }; + Self::from_inner(inner) + } +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs index 81bb43d1498b1347bc55dccb09952b4b1a37bb61..de6df46dc2e71f173b8cbb103680b272a21a654f 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/src/tests.rs @@ -5,31 +5,37 @@ use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; use core::ffi::CStr; +use core::ffi::c_int; use crate::prelude::*; -extern "C" { +unsafe extern "C" { fn myc_callback(x: u64) -> u64; } -#[no_mangle] -pub extern "C" fn do_rust_tests() -> u64 { +pub fn init_tests() -> Result<(), c_int> { + // All of those functions are #[inline(never)] to ensure their name appear in backtraces if any + // issue happens. test_2(); test_3(); test_4(); test_5(); test_6(); + test_7(); + test_8(); pr_info!("Rust: tests finished"); - 0 + Ok(()) } +#[inline(never)] fn test_2() { pr_info!("Rust: test_2"); let x = unsafe { myc_callback(42) }; assert_eq!(x, 43); } +#[inline(never)] fn test_3() { let left = 1; let right = 3; @@ -44,16 +50,18 @@ fn test_3() { assert_eq!(x, 6); } +#[inline(never)] fn test_4() { pr_info!("Rust: test_4"); - let minalign = get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + let minalign = c_eval!("linux/slab.h", "ARCH_KMALLOC_MINALIGN", usize); // Check we don't get any C compilation error with duplicated code. - let minalign2 = get_c_macro!("linux/slab.h", ARCH_KMALLOC_MINALIGN, usize); + let minalign2 = c_eval!("linux/slab.h", "ARCH_KMALLOC_MINALIGN", usize); assert_eq!(minalign, minalign2); assert!(minalign >= 8); } +#[inline(never)] fn test_5() { pr_info!("Rust: test_5"); @@ -134,8 +142,38 @@ fn test_5() { "# } assert_eq!(unsafe { my_cfunc_10() }, None); + + #[cfunc] + unsafe fn my_cfunc_11(buf: &'static [u8]) -> bool { + r#" + + return (buf.len == 11) && (buf.data[0] == 'h') && (buf.data[10] == 'd'); + "# + } + assert!(unsafe { my_cfunc_11(b"hello world") }); + + #[cfunc] + fn my_cfunc_12() -> &'static [u8] { + r#" + unsigned char *s = "hello world"; + struct slice_const_u8 x = {.data = s, .len = strlen(s)}; + return x; + "# + } + assert_eq!(my_cfunc_12(), b"hello world"); + + #[cfunc] + unsafe fn my_cfunc_13<'a>(x: &'a mut [u8]) -> &'a mut [u8] { + r#" + x.data[0] = 'b'; + return x; + "# + } + let mut buf: [u8; 1] = [b'a']; + assert_eq!(unsafe { my_cfunc_13(&mut buf) }, b"b"); } +#[inline(never)] fn test_6() { pr_info!("Rust: test_6"); @@ -146,9 +184,75 @@ fn test_6() { } { - let zst_addr = get_c_macro!("linux/slab.h", ZERO_SIZE_PTR, *const u8); + let zst_addr = c_eval!("linux/slab.h", "ZERO_SIZE_PTR", *const u8); let b = KBox::new(()); - assert_eq!(b.as_ptr().as_ptr() as usize, zst_addr as usize); + assert_eq!(b.as_ptr() as usize, zst_addr as usize); drop(b); } } + +use crate::runtime::sync::new_static_mutex; + +#[inline(never)] +fn test_7() { + pr_info!("Rust: test_7"); + + use crate::runtime::sync::{Lock, Mutex, SpinLock}; + use core::ops::{Deref, DerefMut}; + { + new_static_mutex!(STATIC_MUTEX, u32, 42); + macro_rules! test_lock { + ($lock:expr) => {{ + let lock = $lock; + let mut guard = lock.lock(); + + assert!(lock.is_locked()); + assert_eq!(*guard.deref(), 42); + assert_eq!(*guard.deref_mut(), 42); + + let myref = guard.deref_mut(); + + assert!(lock.is_locked()); + + let _ = *myref; + + assert!(lock.is_locked()); + assert_eq!(*guard.deref(), 42); + assert_eq!(*guard.deref_mut(), 42); + + drop(guard); + + assert!(!lock.is_locked()); + + #[allow(dropping_references)] + drop(lock); + }}; + } + + test_lock!(SpinLock::new(42)); + test_lock!(Mutex::new(42)); + test_lock!(&STATIC_MUTEX); + } + + { + use crate::runtime::sync::Rcu; + let rcu = Rcu::new(42); + assert_eq!(*rcu.lock(), 42); + rcu.update(43); + assert_eq!(*rcu.lock(), 43); + } +} + +#[inline(never)] +fn test_8() { + pr_info!("Rust: test_8"); + use crate::runtime::sysfs::{KObjType, KObject}; + + let root = KObject::module_root(); + + let kobj_type = Arc::new(KObjType::new()); + let kobject = KObject::new(kobj_type.clone()); + let kobject2 = KObject::new(kobj_type.clone()); + kobject.add(Some(&root), "foo"); + kobject2.add(Some(&kobject), "bar"); +} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c index f789a7e337118c13a0790d6bef74b999ed5ec08e..ccf6a945c289911ff9e8eec19fad82cf8f6a9b72 100644 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c +++ b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.c @@ -1,17 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ #include "main.h" -#include "rust/lisakmod/tests.h" +#include "rust/lisakmod/bindings.h" u64 myc_callback(u64 x) { return x + 1; } - -// Use of a function pointer with a global variable forces the compiler to -// issue an indirect call, which exhibits issues with kernels compiled with -// CONFIG_CFI_CLANG -typedef uint64_t fnptr(void); -fnptr *volatile myptr = &do_rust_tests; - -int __attribute__((no_sanitize("kcfi"))) rust_tests(void) { - return (int)myptr(); -} diff --git a/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h b/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h deleted file mode 100644 index 3b79dc08ebd35a50b6b2aa686973e90504c73bd7..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/lisakmod/tests.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef RUST_VALIDATE_H -#define RUST_VALIDATE_H - -#include "rust/lisakmod//cbindgen.h" - -int rust_tests(void); - -#endif /* RUST_VALIDATE_H */ diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml deleted file mode 100644 index c867538672e27ac66c601fc52d4fc811b566225e..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "lisakmodprocmacro" -version = "0.1.0" -edition = "2021" - -[dependencies] -lisakmodprocmacro_macro = { path = "../lisakmodprocmacro_macro" } -const_format = "0.2" diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs deleted file mode 100644 index dc55aa3de24c506e34c7a88ed16538a1e3871e2c..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ -#![no_std] - -pub mod private; - -pub use lisakmodprocmacro_macro::*; diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs deleted file mode 100644 index 6d73e4610630fcb465416c499fe07e7925fd74fd..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/src/private/inlinec.rs +++ /dev/null @@ -1,188 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ - -use core::{ - ffi::{c_char, CStr}, - ptr::{null, null_mut, NonNull}, -}; - -pub trait FfiType { - // TODO: if and when Rust gains const trait methods, we can just define a const function to - // build a type rather than providing a C macro body and C preprocessor machinery to build full - // type names. - const C_DECL: &'static str; - type FfiType; -} - -pub trait FromFfi: FfiType { - unsafe fn from_ffi(x: Self::FfiType) -> Self; -} - -pub trait IntoFfi: FfiType { - fn into_ffi(self) -> Self::FfiType; -} - -macro_rules! transparent_cff { - ($ty:ty, $c_decl:expr) => { - impl FfiType for $ty { - const C_DECL: &'static str = $c_decl; - type FfiType = $ty; - } - - impl FromFfi for $ty { - unsafe fn from_ffi(x: Self::FfiType) -> Self { - x - } - } - - impl IntoFfi for $ty { - fn into_ffi(self) -> Self::FfiType { - self - } - } - }; -} - -transparent_cff!(u8, "BUILTIN_TY_DECL(uint8_t, DECLARATOR)"); -transparent_cff!(u16, "BUILTIN_TY_DECL(uint16_t, DECLARATOR)"); -transparent_cff!(u32, "BUILTIN_TY_DECL(uint32_t, DECLARATOR)"); -transparent_cff!(u64, "BUILTIN_TY_DECL(uint64_t, DECLARATOR)"); - -transparent_cff!(i8, "BUILTIN_TY_DECL(signed char, DECLARATOR)"); -transparent_cff!(i16, "BUILTIN_TY_DECL(int16_t, DECLARATOR)"); -transparent_cff!(i32, "BUILTIN_TY_DECL(int32_t, DECLARATOR)"); -transparent_cff!(i64, "BUILTIN_TY_DECL(int64_t, DECLARATOR)"); - -transparent_cff!(usize, "BUILTIN_TY_DECL(size_t, DECLARATOR)"); -transparent_cff!(isize, "BUILTIN_TY_DECL(ssize_t, DECLARATOR)"); - -transparent_cff!(bool, "BUILTIN_TY_DECL(_Bool, DECLARATOR)"); -transparent_cff!((), "BUILTIN_TY_DECL(void, DECLARATOR)"); - -transparent_cff!( - *const u8, - "BUILTIN_TY_DECL(const uint8_t, PTR_TY_DECL(DECLARATOR))" -); - -transparent_cff!(*mut u8, "BUILTIN_TY_DECL(uint8_t, PTR_TY_DECL(DECLARATOR))"); -transparent_cff!( - *const c_char, - "BUILTIN_TY_DECL(const char, PTR_TY_DECL(DECLARATOR))" -); - -impl FfiType for Option> { - const C_DECL: &'static str = "BUILTIN_TY_DECL(const uint8_t, PTR_TY_DECL(DECLARATOR))"; - type FfiType = *mut u8; -} - -impl IntoFfi for Option> { - #[inline] - fn into_ffi(self) -> Self::FfiType { - match self { - None => null_mut(), - Some(p) => p.as_ptr(), - } - } -} - -impl FromFfi for Option> { - #[inline] - unsafe fn from_ffi(x: Self::FfiType) -> Self { - if x.is_null() { - None - } else { - Some(NonNull::new(x).unwrap()) - } - } -} - -impl FfiType for NonNull { - const C_DECL: &'static str = > as FfiType>::C_DECL; - type FfiType = > as FfiType>::FfiType; -} - -impl IntoFfi for NonNull { - fn into_ffi(self) -> Self::FfiType { - Some(self).into_ffi() - } -} - -impl FromFfi for NonNull { - unsafe fn from_ffi(x: Self::FfiType) -> Self { - let x = > as FromFfi>::from_ffi(x); - x.expect("NULL pointer was passed to NonNull") - } -} - -impl FfiType for Option<&CStr> { - const C_DECL: &'static str = <*const c_char as FfiType>::C_DECL; - type FfiType = <*const c_char as FfiType>::FfiType; -} - -impl IntoFfi for Option<&CStr> { - fn into_ffi(self) -> Self::FfiType { - match self { - None => null(), - Some(s) => s.as_ptr(), - } - } -} - -impl FromFfi for Option<&CStr> { - #[allow(clippy::not_unsafe_ptr_arg_deref)] - unsafe fn from_ffi(x: Self::FfiType) -> Self { - if x.is_null() { - None - } else { - Some(unsafe { CStr::from_ptr(x) }) - } - } -} - -impl<'a> FfiType for &'a CStr { - const C_DECL: &'static str = as FfiType>::C_DECL; - type FfiType = as FfiType>::FfiType; -} - -impl IntoFfi for &CStr { - fn into_ffi(self) -> Self::FfiType { - Some(self).into_ffi() - } -} - -impl FromFfi for &CStr { - unsafe fn from_ffi(x: Self::FfiType) -> Self { - Option::<&CStr>::from_ffi(x) - .expect("NULL pointer was returned as a &CStr, use Option<&CStr> to allow that.") - } -} - -impl<'a> FfiType for &'a str { - const C_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; - type FfiType = <&'a CStr as FfiType>::FfiType; -} - -impl FromFfi for &str { - unsafe fn from_ffi(x: Self::FfiType) -> Self { - <&CStr>::from_ffi(x) - .to_str() - .expect("Invalid UTF-8 content in C string") - } -} - -// 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_DECL: &'static str = <&'a CStr as FfiType>::C_DECL; - type FfiType = <&'a CStr as FfiType>::FfiType; -} - -impl FromFfi for Option<&str> { - unsafe fn from_ffi(x: Self::FfiType) -> Self { - Some( - >::from_ffi(x)? - .to_str() - .expect("Invalid UTF-8 content in C string"), - ) - } -} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs deleted file mode 100644 index 30f79e0f184befbb42b81b7a96cf31462509f991..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro/tests/inlinec.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ - -use lisakmodprocmacro::cfunc; - -#[cfunc] -fn myfunc(x: u64, y: u64) -> u8 { - "#include "; - - r#" - #ifdef FOOBAR - if (x == 3) { - return 1; - } else { - return y; - } - #endif - "# -} - -#[cfunc] -fn myfunc2(x: u64, y: u64) { - "#include "; - - r#" - return; - "# -} - -use core::ffi::CStr; -#[cfunc] -unsafe fn myfunc3<'a, 'b>(x: &'b CStr) -> &'a str -where - 'a: 'b, -{ - "#include "; - - r#" - return; - "# -} - -#[test] -fn test_cfunc() { - assert_eq!(myfunc(1, 2), 2); -} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs deleted file mode 100644 index 61e247b79ec4c11bd9471ed16939a7800255ab51..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/inlinec.rs +++ /dev/null @@ -1,304 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ - -use proc_macro::TokenStream as RustcTokenStream; -use quote::{format_ident, quote}; -use syn::{ - parse::Parse, spanned::Spanned, token, Attribute, Error, FnArg, Generics, Ident, ItemFn, Pat, - ReturnType, Stmt, StmtMacro, Type, -}; - -struct CFuncInput { - name: Ident, - c_code: (Stmt, Stmt, Stmt), - f_args: Vec<(Ident, Type)>, - f_attrs: Vec, - f_ret_ty: Type, - f_generics: Generics, - f_unsafety: Option, -} - -impl Parse for CFuncInput { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let item_fn: ItemFn = input.parse()?; - let span = item_fn.span(); - let f_attrs = item_fn.attrs; - let name = item_fn.sig.ident; - let f_ret_ty = match item_fn.sig.output { - ReturnType::Type(_, ty) => *ty, - ReturnType::Default => syn::parse_str::("()")?, - }; - - let mut snippets = Vec::new(); - for stmt in item_fn.block.stmts { - let snippet = match &stmt { - Stmt::Expr(expr, _) => Ok(Stmt::Expr(expr.clone(), None)), - Stmt::Macro(mac) => { - let mac = mac.clone(); - Ok(Stmt::Macro(StmtMacro { - semi_token: None, - ..mac - })) - } - stmt => Err(Error::new( - stmt.span(), - "An inline C function must contain string expressions.", - )), - }?; - snippets.push(snippet.clone()); - } - - let empty = || Stmt::Expr(syn::parse_str("\"\"").unwrap(), None); - - let c_code = match snippets.len() { - 1 => Ok(( - empty(), - snippets[0].clone(), - empty(), - )), - 2 => Ok(( - snippets[0].clone(), - snippets[1].clone(), - empty(), - )), - 3 => Ok(( - snippets[0].clone(), - snippets[1].clone(), - snippets[2].clone(), - )), - _ => Err(Error::new(span, "An inline C function must contain either a single string expression with the C code in it or two string expressions (one output before the function and one for the body).")) - }?; - - let f_args: Vec<_> = item_fn - .sig - .inputs - .into_iter() - .map(|arg| match arg { - FnArg::Typed(arg) => { - let ident = match *arg.pat { - Pat::Ident(ident) => Ok(ident.ident), - _ => Err(Error::new( - arg.span(), - "An inline C function argument must be an identifier.", - )), - }?; - Ok((ident, *arg.ty)) - } - _ => Err(Error::new( - arg.span(), - "An inline C function argument must have a type.", - )), - }) - .collect::>()?; - - let f_generics = item_fn.sig.generics.clone(); - let f_unsafety = item_fn.sig.unsafety; - - Ok(Self { - name, - c_code, - f_args, - f_attrs, - f_ret_ty, - f_generics, - f_unsafety, - }) - } -} - -pub fn cfunc(_attrs: RustcTokenStream, code: RustcTokenStream) -> Result { - let input = syn::parse::(code)?; - let (pre_c_code, c_code, post_c_code) = input.c_code; - - // TODO: Due to this issue, the span reported by syn is currently always going to have - // span.line == 0, so we workaround that by just emitting a line!() call. This is not as - // precise though. - // https://github.com/dtolnay/proc-macro2/issues/402 - // EDIT: the issue is fixed, let's keep the workaround at hand in case it is needed again as - // this was originally disabled because the rustc API was in flux. - // - // let pre_c_code_line = quote! { line!() }; - // let c_code_line = quote! { line!() }; - // let post_c_code_line = quote! { line!() }; - let pre_c_code_line = pre_c_code.span().start().line; - let c_code_line = c_code.span().start().line; - let post_c_code_line = post_c_code.span().start().line; - - let name = input.name; - let f_ret_ty = input.f_ret_ty; - let f_attrs = input.f_attrs; - let f_args = input.f_args; - let f_unsafety = input.f_unsafety; - let f_generics = input.f_generics; - let f_where = f_generics.where_clause.clone(); - - let section_str = format!(".binstore.c_shims.{}", name); - - // Use getrandom instead of e.g. uuid crate as it has far fewer dependencies, so faster build - // time. - fn get_random() -> u128 { - let mut buf: [u8; 128 / 8] = [0; 128 / 8]; - getrandom::getrandom(&mut buf).expect("Could not get random number"); - u128::from_le_bytes(buf) - } - let c_name_str = format!("__lisa_c_shim_{name}_{}", get_random()); - let c_name = format_ident!("{}", c_name_str); - let c_proto = format!("{c_name_str}_proto"); - let c_ret_ty = format!("{c_name_str}_ret_ty"); - let (c_args, c_args_ty_macros, rust_args, rust_extern_args, rust_extern_call_args) = if f_args - .is_empty() - { - ( - quote! { "void" }, - quote! { "" }, - quote! {}, - quote! {}, - quote! {}, - ) - } else { - let c_nr_args = f_args.len(); - let (arg_names, arg_tys): (Vec<_>, Vec<_>) = f_args.into_iter().unzip(); - let c_arg_names: Vec<_> = arg_names.iter().map(|name| name.to_string()).collect(); - let c_args_commas: Vec<_> = c_arg_names - .iter() - .enumerate() - .map(|(i, _)| if i == (c_nr_args - 1) { "" } else { ", " }) - .collect(); - let c_arg_ty_names: Vec<_> = arg_names - .iter() - .map(|arg| format!("{c_name_str}_arg_ty_{arg}")) - .collect(); - - // Argument types are encoded as a function-like macro. Calling this macro with an - // identifier declares a variable (or function argument) of that type. This allows using - // any type, including more complex ones like arrays and function pointers. - let c_args_ty_macros = quote! { - ::lisakmodprocmacro::private::const_format::concatcp!( - #( - "\n", - ::lisakmodprocmacro::private::const_format::concatcp!( - "#define ", #c_arg_ty_names, "(DECLARATOR)", - { - // Use a const fn to introduce f_generics and f_where - const fn get #f_generics() -> &'static str #f_where { - <#arg_tys as ::lisakmodprocmacro::private::inlinec::FfiType>::C_DECL - } - get() - }, - ) - ),* - ) - }; - - let c_args = quote! { - ::lisakmodprocmacro::private::const_format::concatcp!( - #( - #c_arg_ty_names, - "(", #c_arg_names, ")", #c_args_commas - ),* - ) - }; - let rust_args = quote! { - #( - #arg_names : #arg_tys - ),* - }; - let rust_extern_args = quote! { - #( - #arg_names : <#arg_tys as ::lisakmodprocmacro::private::inlinec::FfiType>::FfiType - ),* - }; - let rust_extern_call_args = quote! { - #( - ::lisakmodprocmacro::private::inlinec::IntoFfi::into_ffi(#arg_names) - ),* - }; - ( - c_args, - c_args_ty_macros, - rust_args, - rust_extern_args, - rust_extern_call_args, - ) - }; - - let out = quote! { - // Store the C function in a section of the binary, that will be extracted by the - // module Makefile and compiled separately as C code. - const _: () = { - // Keep this out of the concatcp!() call so we can have the generic parameters for - // f_ret_ty in scope. This is possible with the "generic_const_item" unstable feature. - const CODE_SLICE: &[u8] = ::lisakmodprocmacro::private::const_format::concatcp!( - r#" - #include - - #define BUILTIN_TY_DECL(ty, declarator) ty (declarator) - #define PTR_TY_DECL(declarator) *(declarator) - #define ARR_TY_DECL(N, declarator) (declarator)[N] - #define FN_PTR_TY_DECL(args, declarator) (*(declarator))args - #define FN_TY_DECL(args, declarator) (declarator)args - "#, - - "#line ", line!(), " \"", file!(), "\"\n", - #c_args_ty_macros, - - // See comment on how arguments type are handled, as we do the same for the return - // type. - "\n#define ", #c_ret_ty, "(DECLARATOR)", - { - // Use a const fn to introduce f_generics and f_where - const fn get #f_generics() -> &'static str #f_where { - <#f_ret_ty as ::lisakmodprocmacro::private::inlinec::FfiType>::C_DECL - } - get() - }, - "\n#define ", #c_proto, " ", #c_ret_ty, "(FN_TY_DECL((", #c_args, "), ", #c_name_str, "))", - "\n", - - "#line ", #pre_c_code_line, " \"", file!(), "\"\n", - #pre_c_code, - "\n", - - // Prototype - "#line ", #c_code_line, " \"", file!(), "\"\n", - #c_proto, ";\n", - // Definition - #c_proto, - "{\n#line ", #c_code_line, " \"", file!(), "\"\n", - #c_code, - "\n}\n", - "#line ", #post_c_code_line, " \"", file!(), "\"\n", - #post_c_code, - "\n", - ).as_bytes(); - const CODE_LEN: usize = CODE_SLICE.len(); - - #[link_section = #section_str ] - #[used] - static CODE: [u8; CODE_LEN] = { - let mut arr = [0u8; CODE_LEN]; - let mut idx: usize = 0; - while idx < CODE_LEN { - arr[idx] = CODE_SLICE[idx]; - idx += 1; - } - arr - }; - }; - - extern "C" { - fn #c_name #f_generics(#rust_extern_args) -> <#f_ret_ty as ::lisakmodprocmacro::private::inlinec::FfiType>::FfiType #f_where; - } - - #[inline] - #(#f_attrs)* - #f_unsafety fn #name #f_generics(#rust_args) -> #f_ret_ty #f_where { - unsafe { - ::lisakmodprocmacro::private::inlinec::FromFfi::from_ffi( - #c_name(#rust_extern_call_args) - ) - } - } - }; - // eprintln!("{}", &out.to_string()); - Ok(out.into()) -} diff --git a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs b/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs deleted file mode 100644 index 2cec4287f46d5c375b6465b10514cbe9e6bd1872..0000000000000000000000000000000000000000 --- a/lisa/_assets/kmodules/lisa/rust/proc_macros/lisakmodprocmacro_macro/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 */ - -use proc_macro::TokenStream; -use syn::Error; - -mod inlinec; - -#[proc_macro_attribute] -pub fn cfunc(attrs: TokenStream, code: TokenStream) -> TokenStream { - inlinec::cfunc(attrs, code).unwrap_or_else(|err| Error::into_compile_error(err).into()) -} diff --git a/lisa/_assets/kmodules/lisa/rustdoc_symbols.py b/lisa/_assets/kmodules/lisa/rust_exported_symbols.py similarity index 58% rename from lisa/_assets/kmodules/lisa/rustdoc_symbols.py rename to lisa/_assets/kmodules/lisa/rust_exported_symbols.py index 3170317789d2594e39d2f000359fa2e4102f1b3f..9f1850cb71152a2ea1286ee8ad62b53fa1b5a3c3 100755 --- a/lisa/_assets/kmodules/lisa/rustdoc_symbols.py +++ b/lisa/_assets/kmodules/lisa/rust_exported_symbols.py @@ -18,38 +18,51 @@ # import argparse -import json from pathlib import Path -import sys from shlex import quote +import subprocess +import os def main(): parser = argparse.ArgumentParser(""" - Parse the JSON output of rustdoc --output-format=json and extract the - exported C symbols. + Get the list of exported Rust functions to make it available to C code (and + not garbage collect these entry points it when linking). """) - parser.add_argument('--rustdoc-json', help='JSON file to parse', required=True) + parser.add_argument('--rust-object', help='Built Rust object file', required=True) parser.add_argument('--out-symbols-plain', help='File to write the symbol list, one per line') parser.add_argument('--out-symbols-cli', help='File to write the symbol list as ld CLI --undefined options') args = parser.parse_args() - path = Path(args.rustdoc_json) - with open(path, 'r') as f: - data = json.load(f) + nm = os.environ.get('NM', 'nm') + out = subprocess.check_output( + [nm, '-gj', args.rust_object], + ) + + # For each symbol we want to export in Rust, we create a companion symbol + # with a prefix that we pick up here. There unfortunately seems to be no + # cleaner way to convey the list of exported symbols from Rust code as of + # 2024. + prefix = b'__export_rust_symbol_' + def parse(sym): + if sym.startswith(prefix): + sym = sym[len(prefix):] + return sym.decode() + else: + return None - items = [ - item - for item in data['index'].values() - if '#[no_mangle]' in item['attrs'] - ] symbols = sorted( - item['name'] - for item in items + sym + for _sym in out.split() + if (sym := parse(_sym)) ) + sep = '\n ' + pretty_symbols = sep.join(symbols) + print(f'Found exported symbols:{sep}{pretty_symbols}') + if (path := args.out_symbols_plain): content = '\n'.join(symbols) + '\n' Path(path).write_text(content) diff --git a/lisa/_kmod.py b/lisa/_kmod.py index 145653d4850ff893ef62d8638a6e2f41fcf5065d..0183856ef8ff3d65ac67332d23e148f80282bf86 100644 --- a/lisa/_kmod.py +++ b/lisa/_kmod.py @@ -3061,11 +3061,8 @@ class FtraceDynamicKmod(DynamicKmod): class _LISADynamicKmodSrc(KmodSrc): _RUST_SPEC = dict( - version='1.81.0', + version='1.82.0', components=[ - # rust-docs to list the C public API (pub #[no_mangle] functions) - # to avoid exporting a thousand symbols from Rust libcore. - 'rust-docs', # rust-src for -Zbuild-std 'rust-src', ]