use std::ffi::CString;
use proc_macro2::{
Literal,
TokenStream, };
use quote::{
format_ident,
quote, };
use syn::{
braced,
bracketed,
ext::IdentExt,
parse::{
Parse,
ParseStream, },
parse_quote,
punctuated::Punctuated,
Error,
Expr,
Ident,
LitStr,
Path,
Result,
Token,
Type, };
use crate::helpers::*;
struct ModInfoBuilder<'a> {
module: &'a str,
counter: usize,
ts: TokenStream,
param_ts: TokenStream,
}
impl<'a> ModInfoBuilder<'a> {
fn new(module: &'a str) -> Self {
ModInfoBuilder {
module,
counter: 0,
ts: TokenStream::new(),
param_ts: TokenStream::new(),
}
}
fn emit_base(&mut self, field: &str, content: &str, builtin: bool, param: bool) {
let string = if builtin {
format!("{module}.{field}={content}\0", module = self.module)
} else {
format!("{field}={content}\0")
};
let length = string.len();
let string = Literal::byte_string(string.as_bytes());
let cfg = if builtin {
quote!(#[cfg(not(MODULE))])
} else {
quote!(#[cfg(MODULE)])
};
let counter = format_ident!(
"__{module}_{counter}",
module = self.module.to_uppercase(),
counter = self.counter
);
let item = quote! {
#cfg
#[cfg_attr(not(target_os = "macos"), link_section = ".modinfo")]
#[used(compiler)]
pub static #counter: [u8; #length] = *#string;
};
if param {
self.param_ts.extend(item);
} else {
self.ts.extend(item);
}
self.counter += 1;
}
fn emit_only_builtin(&mut self, field: &str, content: &str, param: bool) {
self.emit_base(field, content, true, param)
}
fn emit_only_loadable(&mut self, field: &str, content: &str, param: bool) {
self.emit_base(field, content, false, param)
}
fn emit(&mut self, field: &str, content: &str) {
self.emit_internal(field, content, false);
}
fn emit_internal(&mut self, field: &str, content: &str, param: bool) {
self.emit_only_builtin(field, content, param);
self.emit_only_loadable(field, content, param);
}
fn emit_param(&mut self, field: &str, param: &str, content: &str) {
let content = format!("{param}:{content}");
self.emit_internal(field, &content, true);
}
fn emit_params(&mut self, info: &ModuleInfo) {
let Some(params) = &info.params else {
return;
};
for param in params {
let param_name_str = param.name.to_string();
let param_type_str = param.ptype.to_string();
let ops = param_ops_path(¶m_type_str);
self.emit_param("parmtype", ¶m_name_str, ¶m_type_str);
self.emit_param("parm", ¶m_name_str, ¶m.description.value());
let static_name = format_ident!("__{}_{}_struct", self.module, param.name);
let param_name_cstr =
CString::new(param_name_str).expect("name contains NUL-terminator");
let param_name_cstr_with_module =
CString::new(format!("{}.{}", self.module, param.name))
.expect("name contains NUL-terminator");
let param_name = ¶m.name;
let param_type = ¶m.ptype;
let param_default = ¶m.default;
self.param_ts.extend(quote! {
#[allow(non_upper_case_globals)]
pub(crate) static #param_name:
::kernel::module_param::ModuleParamAccess<#param_type> =
::kernel::module_param::ModuleParamAccess::new(#param_default);
const _: () = {
#[allow(non_upper_case_globals)]
#[link_section = "__param"]
#[used(compiler)]
static #static_name:
::kernel::module_param::KernelParam =
::kernel::module_param::KernelParam::new(
::kernel::bindings::kernel_param {
name: kernel::str::as_char_ptr_in_const_context(
if ::core::cfg!(MODULE) {
#param_name_cstr
} else {
#param_name_cstr_with_module
}
),
#[cfg(MODULE)]
mod_: unsafe {
core::ptr::from_ref(&::kernel::bindings::__this_module)
.cast_mut()
},
#[cfg(not(MODULE))]
mod_: ::core::ptr::null_mut(),
ops: core::ptr::from_ref(&#ops),
perm: 0, level: -1,
flags: 0,
__bindgen_anon_1: ::kernel::bindings::kernel_param__bindgen_ty_1 {
arg: #param_name.as_void_ptr()
},
}
);
};
});
}
}
}
fn param_ops_path(param_type: &str) -> Path {
match param_type {
"i8" => parse_quote!(::kernel::module_param::PARAM_OPS_I8),
"u8" => parse_quote!(::kernel::module_param::PARAM_OPS_U8),
"i16" => parse_quote!(::kernel::module_param::PARAM_OPS_I16),
"u16" => parse_quote!(::kernel::module_param::PARAM_OPS_U16),
"i32" => parse_quote!(::kernel::module_param::PARAM_OPS_I32),
"u32" => parse_quote!(::kernel::module_param::PARAM_OPS_U32),
"i64" => parse_quote!(::kernel::module_param::PARAM_OPS_I64),
"u64" => parse_quote!(::kernel::module_param::PARAM_OPS_U64),
"isize" => parse_quote!(::kernel::module_param::PARAM_OPS_ISIZE),
"usize" => parse_quote!(::kernel::module_param::PARAM_OPS_USIZE),
t => panic!("Unsupported parameter type {}", t),
}
}
macro_rules! parse_ordered_fields {
(@gen
[$input:expr]
[$([$name:ident; $key:ident; $parser:expr])*]
[$([$req_name:ident; $req_key:ident])*]
) => {
$(let mut $name = None;)*
const EXPECTED_KEYS: &[&str] = &[$(stringify!($key),)*];
const REQUIRED_KEYS: &[&str] = &[$(stringify!($req_key),)*];
let span = $input.span();
let mut seen_keys = Vec::new();
while !$input.is_empty() {
let key = $input.call(Ident::parse_any)?;
if seen_keys.contains(&key) {
Err(Error::new_spanned(
&key,
format!(r#"duplicated key "{key}". Keys can only be specified once."#),
))?
}
$input.parse::<Token![:]>()?;
match &*key.to_string() {
$(
stringify!($key) => $name = Some($parser),
)*
_ => {
Err(Error::new_spanned(
&key,
format!(r#"unknown key "{key}". Valid keys are: {EXPECTED_KEYS:?}."#),
))?
}
}
$input.parse::<Token![,]>()?;
seen_keys.push(key);
}
for key in REQUIRED_KEYS {
if !seen_keys.iter().any(|e| e == key) {
Err(Error::new(span, format!(r#"missing required key "{key}""#)))?
}
}
let mut ordered_keys: Vec<&str> = Vec::new();
for key in EXPECTED_KEYS {
if seen_keys.iter().any(|e| e == key) {
ordered_keys.push(key);
}
}
if seen_keys != ordered_keys {
Err(Error::new(
span,
format!(r#"keys are not ordered as expected. Order them like: {ordered_keys:?}."#),
))?
}
$(let $req_name = $req_name.expect("required field");)*
};
(@gen
[$input:expr] [$($tok:tt)*] [$($req:tt)*]
$key:ident as $name:ident [required] => $parser:expr,
$($rest:tt)*
) => {
parse_ordered_fields!(
@gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)* [$name; $key]] $($rest)*
)
};
(@gen
[$input:expr] [$($tok:tt)*] [$($req:tt)*]
$name:ident [required] => $parser:expr,
$($rest:tt)*
) => {
parse_ordered_fields!(
@gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)* [$name; $name]] $($rest)*
)
};
(@gen
[$input:expr] [$($tok:tt)*] [$($req:tt)*]
$key:ident as $name:ident => $parser:expr,
$($rest:tt)*
) => {
parse_ordered_fields!(
@gen [$input] [$($tok)* [$name; $key; $parser]] [$($req)*] $($rest)*
)
};
(@gen
[$input:expr] [$($tok:tt)*] [$($req:tt)*]
$name:ident => $parser:expr,
$($rest:tt)*
) => {
parse_ordered_fields!(
@gen [$input] [$($tok)* [$name; $name; $parser]] [$($req)*] $($rest)*
)
};
(from $input:expr; $($tok:tt)*) => {
parse_ordered_fields!(@gen [$input] [] [] $($tok)*)
}
}
struct Parameter {
name: Ident,
ptype: Ident,
default: Expr,
description: LitStr,
}
impl Parse for Parameter {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let name = input.parse()?;
input.parse::<Token![:]>()?;
let ptype = input.parse()?;
let fields;
braced!(fields in input);
parse_ordered_fields! {
from fields;
default [required] => fields.parse()?,
description [required] => fields.parse()?,
}
Ok(Self {
name,
ptype,
default,
description,
})
}
}
pub(crate) struct ModuleInfo {
type_: Type,
license: AsciiLitStr,
name: AsciiLitStr,
authors: Option<Punctuated<AsciiLitStr, Token![,]>>,
description: Option<LitStr>,
alias: Option<Punctuated<AsciiLitStr, Token![,]>>,
firmware: Option<Punctuated<AsciiLitStr, Token![,]>>,
imports_ns: Option<Punctuated<AsciiLitStr, Token![,]>>,
params: Option<Punctuated<Parameter, Token![,]>>,
}
impl Parse for ModuleInfo {
fn parse(input: ParseStream<'_>) -> Result<Self> {
parse_ordered_fields!(
from input;
type as type_ [required] => input.parse()?,
name [required] => input.parse()?,
authors => {
let list;
bracketed!(list in input);
Punctuated::parse_terminated(&list)?
},
description => input.parse()?,
license [required] => input.parse()?,
alias => {
let list;
bracketed!(list in input);
Punctuated::parse_terminated(&list)?
},
firmware => {
let list;
bracketed!(list in input);
Punctuated::parse_terminated(&list)?
},
imports_ns => {
let list;
bracketed!(list in input);
Punctuated::parse_terminated(&list)?
},
params => {
let list;
braced!(list in input);
Punctuated::parse_terminated(&list)?
},
);
Ok(ModuleInfo {
type_,
license,
name,
authors,
description,
alias,
firmware,
imports_ns,
params,
})
}
}
pub(crate) fn module(info: ModuleInfo) -> Result<TokenStream> {
let ModuleInfo {
type_,
license,
name,
authors,
description,
alias,
firmware,
imports_ns,
params: _,
} = &info;
let ident = name.value().replace('-', "_");
let mut modinfo = ModInfoBuilder::new(ident.as_ref());
if let Some(authors) = authors {
for author in authors {
modinfo.emit("author", &author.value());
}
}
if let Some(description) = description {
modinfo.emit("description", &description.value());
}
modinfo.emit("license", &license.value());
if let Some(aliases) = alias {
for alias in aliases {
modinfo.emit("alias", &alias.value());
}
}
if let Some(firmware) = firmware {
for fw in firmware {
modinfo.emit("firmware", &fw.value());
}
}
if let Some(imports) = imports_ns {
for ns in imports {
modinfo.emit("import_ns", &ns.value());
}
}
let file =
std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable");
modinfo.emit_only_builtin("file", &file, false);
modinfo.emit_params(&info);
let modinfo_ts = modinfo.ts;
let params_ts = modinfo.param_ts;
let ident_init = format_ident!("__{ident}_init");
let ident_exit = format_ident!("__{ident}_exit");
let ident_initcall = format_ident!("__{ident}_initcall");
let initcall_section = ".initcall6.init";
let global_asm = format!(
r#".section "{initcall_section}", "a"
__{ident}_initcall:
.long __{ident}_init - .
.previous
"#
);
let name_cstr = CString::new(name.value()).expect("name contains NUL-terminator");
Ok(quote! {
const __LOG_PREFIX: &[u8] = #name_cstr.to_bytes_with_nul();
#[cfg(MODULE)]
static THIS_MODULE: ::kernel::ThisModule = unsafe {
extern "C" {
static __this_module: ::kernel::types::Opaque<::kernel::bindings::module>;
};
::kernel::ThisModule::from_ptr(__this_module.get())
};
#[cfg(not(MODULE))]
static THIS_MODULE: ::kernel::ThisModule = unsafe {
::kernel::ThisModule::from_ptr(::core::ptr::null_mut())
};
type LocalModule = #type_;
impl ::kernel::ModuleMetadata for #type_ {
const NAME: &'static ::kernel::str::CStr = #name_cstr;
}
#[doc(hidden)]
mod __module_init {
mod __module_init {
use pin_init::PinInit;
#[cfg(MODULE)]
#[used(compiler)]
static __IS_RUST_MODULE: () = ();
static mut __MOD: ::core::mem::MaybeUninit<super::super::LocalModule> =
::core::mem::MaybeUninit::uninit();
#[cfg(MODULE)]
#[no_mangle]
#[link_section = ".init.text"]
pub unsafe extern "C" fn init_module() -> ::kernel::ffi::c_int {
unsafe { __init() }
}
#[cfg(MODULE)]
#[used(compiler)]
#[link_section = ".init.data"]
static __UNIQUE_ID___addressable_init_module: unsafe extern "C" fn() -> i32 =
init_module;
#[cfg(MODULE)]
#[no_mangle]
#[link_section = ".exit.text"]
pub extern "C" fn cleanup_module() {
unsafe { __exit() }
}
#[cfg(MODULE)]
#[used(compiler)]
#[link_section = ".exit.data"]
static __UNIQUE_ID___addressable_cleanup_module: extern "C" fn() = cleanup_module;
#[cfg(not(MODULE))]
#[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
#[link_section = #initcall_section]
#[used(compiler)]
pub static #ident_initcall: extern "C" fn() ->
::kernel::ffi::c_int = #ident_init;
#[cfg(not(MODULE))]
#[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
::core::arch::global_asm!(#global_asm);
#[cfg(not(MODULE))]
#[no_mangle]
pub extern "C" fn #ident_init() -> ::kernel::ffi::c_int {
unsafe { __init() }
}
#[cfg(not(MODULE))]
#[no_mangle]
pub extern "C" fn #ident_exit() {
unsafe { __exit() }
}
unsafe fn __init() -> ::kernel::ffi::c_int {
let initer = <super::super::LocalModule as ::kernel::InPlaceModule>::init(
&super::super::THIS_MODULE
);
match unsafe { initer.__pinned_init(__MOD.as_mut_ptr()) } {
Ok(m) => 0,
Err(e) => e.to_errno(),
}
}
unsafe fn __exit() {
unsafe {
__MOD.assume_init_drop();
}
}
#modinfo_ts
}
}
mod module_parameters {
#params_ts
}
})
}