From: Miguel Ojeda <ojeda@kernel.org>
To: Linus Torvalds <torvalds@linux-foundation.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-fsdevel@vger.kernel.org, patches@lists.linux.dev,
"Jarkko Sakkinen" <jarkko@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Finn Behrens" <me@kloenk.de>,
"Adam Bratschi-Kaye" <ark.email@gmail.com>,
"Wedson Almeida Filho" <wedsonaf@google.com>,
"Sumera Priyadarsini" <sylphrenadin@gmail.com>,
"Gary Guo" <gary@garyguo.net>, "Matthew Bakhtiari" <dev@mtbk.me>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>
Subject: [PATCH v9 10/27] rust: add `macros` crate
Date: Fri, 5 Aug 2022 17:41:55 +0200 [thread overview]
Message-ID: <20220805154231.31257-11-ojeda@kernel.org> (raw)
In-Reply-To: <20220805154231.31257-1-ojeda@kernel.org>
This crate contains all the procedural macros ("proc macros")
shared by all the kernel.
Procedural macros allow to create syntax extensions. They run at
compile-time and can consume as well as produce Rust syntax.
For instance, the `module!` macro that is used by Rust modules
is implemented here. It allows to easily declare the equivalent
information to the `MODULE_*` macros in C modules, e.g.:
module! {
type: RustMinimal,
name: b"rust_minimal",
author: b"Rust for Linux Contributors",
description: b"Rust minimal sample",
license: b"GPL",
}
Co-developed-by: Alex Gaynor <alex.gaynor@gmail.com>
Signed-off-by: Alex Gaynor <alex.gaynor@gmail.com>
Co-developed-by: Finn Behrens <me@kloenk.de>
Signed-off-by: Finn Behrens <me@kloenk.de>
Co-developed-by: Adam Bratschi-Kaye <ark.email@gmail.com>
Signed-off-by: Adam Bratschi-Kaye <ark.email@gmail.com>
Co-developed-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Wedson Almeida Filho <wedsonaf@google.com>
Co-developed-by: Sumera Priyadarsini <sylphrenadin@gmail.com>
Signed-off-by: Sumera Priyadarsini <sylphrenadin@gmail.com>
Co-developed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Gary Guo <gary@garyguo.net>
Co-developed-by: Matthew Bakhtiari <dev@mtbk.me>
Signed-off-by: Matthew Bakhtiari <dev@mtbk.me>
Co-developed-by: Björn Roy Baron <bjorn3_gh@protonmail.com>
Signed-off-by: Björn Roy Baron <bjorn3_gh@protonmail.com>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
---
rust/macros/helpers.rs | 51 ++++++++
rust/macros/lib.rs | 72 +++++++++++
rust/macros/module.rs | 282 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 405 insertions(+)
create mode 100644 rust/macros/helpers.rs
create mode 100644 rust/macros/lib.rs
create mode 100644 rust/macros/module.rs
diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
new file mode 100644
index 000000000000..cdc7dc6135d2
--- /dev/null
+++ b/rust/macros/helpers.rs
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro::{token_stream, TokenTree};
+
+pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
+ if let Some(TokenTree::Ident(ident)) = it.next() {
+ Some(ident.to_string())
+ } else {
+ None
+ }
+}
+
+pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
+ if let Some(TokenTree::Literal(literal)) = it.next() {
+ Some(literal.to_string())
+ } else {
+ None
+ }
+}
+
+pub(crate) fn try_byte_string(it: &mut token_stream::IntoIter) -> Option<String> {
+ try_literal(it).and_then(|byte_string| {
+ if byte_string.starts_with("b\"") && byte_string.ends_with('\"') {
+ Some(byte_string[2..byte_string.len() - 1].to_string())
+ } else {
+ None
+ }
+ })
+}
+
+pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
+ try_ident(it).expect("Expected Ident")
+}
+
+pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
+ if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
+ punct.as_char()
+ } else {
+ panic!("Expected Punct");
+ }
+}
+
+pub(crate) fn expect_byte_string(it: &mut token_stream::IntoIter) -> String {
+ try_byte_string(it).expect("Expected byte string")
+}
+
+pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
+ if it.next().is_some() {
+ panic!("Expected end");
+ }
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
new file mode 100644
index 000000000000..91764bfb1f89
--- /dev/null
+++ b/rust/macros/lib.rs
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Crate for all kernel procedural macros.
+
+mod helpers;
+mod module;
+
+use proc_macro::TokenStream;
+
+/// Declares a kernel module.
+///
+/// The `type` argument should be a type which implements the [`Module`]
+/// trait. Also accepts various forms of kernel metadata.
+///
+/// C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h)
+///
+/// [`Module`]: ../kernel/trait.Module.html
+///
+/// # Examples
+///
+/// ```ignore
+/// use kernel::prelude::*;
+///
+/// module!{
+/// type: MyModule,
+/// name: b"my_kernel_module",
+/// author: b"Rust for Linux Contributors",
+/// description: b"My very own kernel module!",
+/// license: b"GPL",
+/// params: {
+/// my_i32: i32 {
+/// default: 42,
+/// permissions: 0o000,
+/// description: b"Example of i32",
+/// },
+/// writeable_i32: i32 {
+/// default: 42,
+/// permissions: 0o644,
+/// description: b"Example of i32",
+/// },
+/// },
+/// }
+///
+/// struct MyModule;
+///
+/// impl kernel::Module for MyModule {
+/// fn init() -> Result<Self> {
+/// // If the parameter is writeable, then the kparam lock must be
+/// // taken to read the parameter:
+/// {
+/// let lock = THIS_MODULE.kernel_param_lock();
+/// pr_info!("i32 param is: {}\n", writeable_i32.read(&lock));
+/// }
+/// // If the parameter is read only, it can be read without locking
+/// // the kernel parameters:
+/// pr_info!("i32 param is: {}\n", my_i32.read());
+/// Ok(Self)
+/// }
+/// }
+/// ```
+///
+/// # Supported argument types
+/// - `type`: type which implements the [`Module`] trait (required).
+/// - `name`: byte array of the name of the kernel module (required).
+/// - `author`: byte array of the author of the kernel module.
+/// - `description`: byte array of the description of the kernel module.
+/// - `license`: byte array of the license of the kernel module (required).
+/// - `alias`: byte array of alias name of the kernel module.
+#[proc_macro]
+pub fn module(ts: TokenStream) -> TokenStream {
+ module::module(ts)
+}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
new file mode 100644
index 000000000000..186a5b8be23c
--- /dev/null
+++ b/rust/macros/module.rs
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use crate::helpers::*;
+use proc_macro::{token_stream, Literal, TokenStream, TokenTree};
+use std::fmt::Write;
+
+struct ModInfoBuilder<'a> {
+ module: &'a str,
+ counter: usize,
+ buffer: String,
+}
+
+impl<'a> ModInfoBuilder<'a> {
+ fn new(module: &'a str) -> Self {
+ ModInfoBuilder {
+ module,
+ counter: 0,
+ buffer: String::new(),
+ }
+ }
+
+ fn emit_base(&mut self, field: &str, content: &str, builtin: bool) {
+ let string = if builtin {
+ // Built-in modules prefix their modinfo strings by `module.`.
+ format!(
+ "{module}.{field}={content}\0",
+ module = self.module,
+ field = field,
+ content = content
+ )
+ } else {
+ // Loadable modules' modinfo strings go as-is.
+ format!("{field}={content}\0", field = field, content = content)
+ };
+
+ write!(
+ &mut self.buffer,
+ "
+ {cfg}
+ #[doc(hidden)]
+ #[link_section = \".modinfo\"]
+ #[used]
+ pub static __{module}_{counter}: [u8; {length}] = *{string};
+ ",
+ cfg = if builtin {
+ "#[cfg(not(MODULE))]"
+ } else {
+ "#[cfg(MODULE)]"
+ },
+ module = self.module.to_uppercase(),
+ counter = self.counter,
+ length = string.len(),
+ string = Literal::byte_string(string.as_bytes()),
+ )
+ .unwrap();
+
+ self.counter += 1;
+ }
+
+ fn emit_only_builtin(&mut self, field: &str, content: &str) {
+ self.emit_base(field, content, true)
+ }
+
+ fn emit_only_loadable(&mut self, field: &str, content: &str) {
+ self.emit_base(field, content, false)
+ }
+
+ fn emit(&mut self, field: &str, content: &str) {
+ self.emit_only_builtin(field, content);
+ self.emit_only_loadable(field, content);
+ }
+}
+
+#[derive(Debug, Default)]
+struct ModuleInfo {
+ type_: String,
+ license: String,
+ name: String,
+ author: Option<String>,
+ description: Option<String>,
+ alias: Option<String>,
+}
+
+impl ModuleInfo {
+ fn parse(it: &mut token_stream::IntoIter) -> Self {
+ let mut info = ModuleInfo::default();
+
+ const EXPECTED_KEYS: &[&str] =
+ &["type", "name", "author", "description", "license", "alias"];
+ const REQUIRED_KEYS: &[&str] = &["type", "name", "license"];
+ let mut seen_keys = Vec::new();
+
+ loop {
+ let key = match it.next() {
+ Some(TokenTree::Ident(ident)) => ident.to_string(),
+ Some(_) => panic!("Expected Ident or end"),
+ None => break,
+ };
+
+ if seen_keys.contains(&key) {
+ panic!(
+ "Duplicated key \"{}\". Keys can only be specified once.",
+ key
+ );
+ }
+
+ assert_eq!(expect_punct(it), ':');
+
+ match key.as_str() {
+ "type" => info.type_ = expect_ident(it),
+ "name" => info.name = expect_byte_string(it),
+ "author" => info.author = Some(expect_byte_string(it)),
+ "description" => info.description = Some(expect_byte_string(it)),
+ "license" => info.license = expect_byte_string(it),
+ "alias" => info.alias = Some(expect_byte_string(it)),
+ _ => panic!(
+ "Unknown key \"{}\". Valid keys are: {:?}.",
+ key, EXPECTED_KEYS
+ ),
+ }
+
+ assert_eq!(expect_punct(it), ',');
+
+ seen_keys.push(key);
+ }
+
+ expect_end(it);
+
+ for key in REQUIRED_KEYS {
+ if !seen_keys.iter().any(|e| e == key) {
+ panic!("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 {
+ panic!(
+ "Keys are not ordered as expected. Order them like: {:?}.",
+ ordered_keys
+ );
+ }
+
+ info
+ }
+}
+
+pub(crate) fn module(ts: TokenStream) -> TokenStream {
+ let mut it = ts.into_iter();
+
+ let info = ModuleInfo::parse(&mut it);
+
+ let mut modinfo = ModInfoBuilder::new(info.name.as_ref());
+ if let Some(author) = info.author {
+ modinfo.emit("author", &author);
+ }
+ if let Some(description) = info.description {
+ modinfo.emit("description", &description);
+ }
+ modinfo.emit("license", &info.license);
+ if let Some(alias) = info.alias {
+ modinfo.emit("alias", &alias);
+ }
+
+ // Built-in modules also export the `file` modinfo string.
+ let file =
+ std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable");
+ modinfo.emit_only_builtin("file", &file);
+
+ format!(
+ "
+ /// The module name.
+ ///
+ /// Used by the printing macros, e.g. [`info!`].
+ const __LOG_PREFIX: &[u8] = b\"{name}\\0\";
+
+ /// The \"Rust loadable module\" mark, for `scripts/is_rust_module.sh`.
+ //
+ // This may be best done another way later on, e.g. as a new modinfo
+ // key or a new section. For the moment, keep it simple.
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[used]
+ static __IS_RUST_MODULE: () = ();
+
+ static mut __MOD: Option<{type_}> = None;
+
+ // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
+ // freed until the module is unloaded.
+ #[cfg(MODULE)]
+ static THIS_MODULE: kernel::ThisModule = unsafe {{
+ kernel::ThisModule::from_ptr(&kernel::bindings::__this_module as *const _ as *mut _)
+ }};
+ #[cfg(not(MODULE))]
+ static THIS_MODULE: kernel::ThisModule = unsafe {{
+ kernel::ThisModule::from_ptr(core::ptr::null_mut())
+ }};
+
+ // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern \"C\" fn init_module() -> core::ffi::c_int {{
+ __init()
+ }}
+
+ #[cfg(MODULE)]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern \"C\" fn cleanup_module() {{
+ __exit()
+ }}
+
+ // Built-in modules are initialized through an initcall pointer
+ // and the identifiers need to be unique.
+ #[cfg(not(MODULE))]
+ #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
+ #[doc(hidden)]
+ #[link_section = \"{initcall_section}\"]
+ #[used]
+ pub static __{name}_initcall: extern \"C\" fn() -> core::ffi::c_int = __{name}_init;
+
+ #[cfg(not(MODULE))]
+ #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
+ core::arch::global_asm!(
+ r#\".section \"{initcall_section}\", \"a\"
+ __{name}_initcall:
+ .long __{name}_init - .
+ .previous
+ \"#
+ );
+
+ #[cfg(not(MODULE))]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern \"C\" fn __{name}_init() -> core::ffi::c_int {{
+ __init()
+ }}
+
+ #[cfg(not(MODULE))]
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern \"C\" fn __{name}_exit() {{
+ __exit()
+ }}
+
+ fn __init() -> core::ffi::c_int {{
+ match <{type_} as kernel::Module>::init(&THIS_MODULE) {{
+ Ok(m) => {{
+ unsafe {{
+ __MOD = Some(m);
+ }}
+ return 0;
+ }}
+ Err(e) => {{
+ return e.to_kernel_errno();
+ }}
+ }}
+ }}
+
+ fn __exit() {{
+ unsafe {{
+ // Invokes `drop()` on `__MOD`, which should be used for cleanup.
+ __MOD = None;
+ }}
+ }}
+
+ {modinfo}
+ ",
+ type_ = info.type_,
+ name = info.name,
+ modinfo = modinfo.buffer,
+ initcall_section = ".initcall6.init"
+ )
+ .parse()
+ .expect("Error parsing formatted string into token stream.")
+}
--
2.37.1
next prev parent reply other threads:[~2022-08-05 15:45 UTC|newest]
Thread overview: 103+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-05 15:41 [PATCH v9 00/27] Rust support Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 01/27] kallsyms: use `sizeof` instead of hardcoded size Miguel Ojeda
2022-08-05 16:48 ` Geert Stappers
2022-08-05 18:46 ` Miguel Ojeda
2022-08-05 22:40 ` Konstantin Shelekhin
2022-08-17 19:36 ` Kees Cook
2022-08-18 9:03 ` Konstantin Shelekhin
2022-08-18 16:03 ` Kees Cook
2022-09-27 12:48 ` Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 02/27] kallsyms: avoid hardcoding buffer size Miguel Ojeda
2022-08-17 19:37 ` Kees Cook
2022-08-18 16:50 ` Geert Stappers
2022-08-05 15:41 ` [PATCH v9 03/27] kallsyms: add static relationship between `KSYM_NAME_LEN{,_BUFFER}` Miguel Ojeda
2022-08-17 19:39 ` Kees Cook
2022-08-17 19:50 ` Boqun Feng
2022-08-17 20:31 ` Kees Cook
2022-08-17 20:45 ` Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 04/27] kallsyms: support "big" kernel symbols Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 05/27] kallsyms: increase maximum kernel symbol length to 512 Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 06/27] rust: add C helpers Miguel Ojeda
2022-08-17 19:44 ` Kees Cook
2022-08-17 20:22 ` Miguel Ojeda
2022-08-17 20:34 ` Kees Cook
2022-08-17 21:44 ` Miguel Ojeda
2022-08-17 23:56 ` Kees Cook
2022-08-18 16:03 ` Miguel Ojeda
2022-08-18 16:08 ` Kees Cook
2022-08-18 17:01 ` Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 07/27] rust: import upstream `alloc` crate Miguel Ojeda
2022-08-17 20:07 ` Kees Cook
2022-08-17 21:00 ` Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 08/27] rust: adapt `alloc` crate to the kernel Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 09/27] rust: add `compiler_builtins` crate Miguel Ojeda
2022-08-17 20:08 ` Kees Cook
2022-08-22 23:55 ` Nick Desaulniers
2022-08-24 18:38 ` Nick Desaulniers
2022-08-29 17:11 ` Gary Guo
2022-08-05 15:41 ` Miguel Ojeda [this message]
2022-08-05 15:41 ` [PATCH v9 11/27] rust: add `bindings` crate Miguel Ojeda
2022-08-05 15:41 ` [PATCH v9 12/27] rust: add `kernel` crate Miguel Ojeda
2022-08-06 10:24 ` Konstantin Shelekhin
2022-08-06 11:22 ` Miguel Ojeda
2022-08-06 12:15 ` Konstantin Shelekhin
2022-08-06 14:57 ` Matthew Wilcox
2022-09-19 14:07 ` Wedson Almeida Filho
2022-09-19 16:09 ` Linus Torvalds
2022-09-19 17:20 ` Linus Torvalds
2022-09-19 18:05 ` Wedson Almeida Filho
2022-09-19 20:42 ` Linus Torvalds
2022-09-19 22:35 ` Wedson Almeida Filho
2022-09-19 23:39 ` Linus Torvalds
2022-09-19 23:50 ` Alex Gaynor
2022-09-19 23:58 ` Linus Torvalds
2022-09-20 0:15 ` Linus Torvalds
2022-09-20 15:55 ` Eric W. Biederman
2022-09-20 22:39 ` Gary Guo
2022-09-21 6:42 ` comex
2022-09-21 14:19 ` Boqun Feng
2022-10-03 2:03 ` comex
2022-09-20 0:40 ` Boqun Feng
2022-10-03 4:17 ` Kyle Strand
2022-09-20 0:41 ` Wedson Almeida Filho
2022-09-21 11:23 ` Konstantin Shelekhin
2022-09-21 11:46 ` Greg KH
2022-08-05 15:41 ` [PATCH v9 13/27] rust: export generated symbols Miguel Ojeda
2022-08-17 20:11 ` Kees Cook
2022-08-05 15:41 ` [PATCH v9 14/27] vsprintf: add new `%pA` format specifier Miguel Ojeda
2022-08-05 15:42 ` [PATCH v9 15/27] scripts: checkpatch: diagnose uses of `%pA` in the C side as errors Miguel Ojeda
2022-08-05 15:42 ` [PATCH v9 16/27] scripts: checkpatch: enable language-independent checks for Rust Miguel Ojeda
2022-08-17 20:12 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 17/27] scripts: decode_stacktrace: demangle Rust symbols Miguel Ojeda
2022-08-05 15:42 ` [PATCH v9 18/27] scripts: add `generate_rust_analyzer.py` Miguel Ojeda
2022-08-17 20:13 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 19/27] scripts: add `generate_rust_target.rs` Miguel Ojeda
2022-08-17 20:14 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 20/27] scripts: add `rust_is_available.sh` Miguel Ojeda
2022-08-17 20:18 ` Kees Cook
2022-08-17 20:40 ` Miguel Ojeda
2022-08-22 20:09 ` Nick Desaulniers
2022-08-23 12:12 ` Miguel Ojeda
2022-08-23 12:16 ` Miguel Ojeda
2022-08-05 15:42 ` [PATCH v9 21/27] scripts: add `is_rust_module.sh` Miguel Ojeda
2022-08-17 20:19 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 22/27] rust: add `.rustfmt.toml` Miguel Ojeda
2022-08-17 20:19 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 23/27] Kbuild: add Rust support Miguel Ojeda
2022-08-17 20:26 ` Kees Cook
2022-08-17 20:56 ` Miguel Ojeda
2022-08-22 22:35 ` Nick Desaulniers
2022-09-12 16:07 ` Masahiro Yamada
2022-09-12 16:18 ` Miguel Ojeda
2022-09-13 6:37 ` Masahiro Yamada
2022-08-05 15:42 ` [PATCH v9 24/27] docs: add Rust documentation Miguel Ojeda
2022-08-05 15:42 ` [PATCH v9 25/27] x86: enable initial Rust support Miguel Ojeda
2022-08-17 20:27 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 26/27] samples: add first Rust examples Miguel Ojeda
2022-08-06 13:14 ` Konstantin Shelekhin
2022-08-17 21:02 ` Miguel Ojeda
2022-08-18 9:04 ` Konstantin Shelekhin
2022-08-17 20:28 ` Kees Cook
2022-08-05 15:42 ` [PATCH v9 27/27] MAINTAINERS: Rust Miguel Ojeda
2022-08-17 20:28 ` Kees Cook
2022-08-17 20:43 ` Miguel Ojeda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220805154231.31257-11-ojeda@kernel.org \
--to=ojeda@kernel.org \
--cc=alex.gaynor@gmail.com \
--cc=ark.email@gmail.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dev@mtbk.me \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=jarkko@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=me@kloenk.de \
--cc=patches@lists.linux.dev \
--cc=rust-for-linux@vger.kernel.org \
--cc=sylphrenadin@gmail.com \
--cc=torvalds@linux-foundation.org \
--cc=wedsonaf@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).