From 40cbc3f043eeea8b8f27ece943f42437a264239b Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Tue, 24 Sep 2019 01:14:15 +0200 Subject: Initial Rust support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we have several lines of work:   - Integrating with the kernel tree and build system (Nick's & mine, both uploaded as branches, based on `rustc`; and another one I have been working on, based on `cargo`).   - Bindings and the first bits of functionality (Alex's & Geoffrey's, based on `cargo`). This patch effectively merges the work we have been doing and integrates it in the latest mainline kernel tree. This does *not* mean anything needs to stay as-is, but this gives us a working, common base to work and experiment upon. Hopefully, it will also attract external people to join! As a summary, I added: - `cargo` integration with the kernel `Makefile`s:   + Virtual `cargo` workspace to have a single lock file and to share deps between `cargo` jobs. + Picks the same optimization level as configured for C. + Verbose output on `V=1`. + A `cargoclean` target to clean all the Rust-related artifacts. - Initial support for built-in modules (i.e. `Y` as well as `M`): + It is a hack, we need to implement a few things (see the `TODO`s), but it is enough to start playing with things that depend on `MODULE`. + Passes `--cfg module` to built-in modules to be able to compile conditionally inside Rust. + Increased `KSYM_NAME_LEN` length to avoid warnings due to Rust long mangled symbols.   - Rust infrastructure in a new top level folder `rust/`: + A `kernel` package which contains the sources from Alex & Geoffrey's work plus some changes:     * Adapted `build.rs`. * Removed the `THIS_MODULE` emulation until it is implemented.     * Removed `Makefile` logic and the code that `cfg`-depended on kernel version (no need in mainline). * Moved the helpers to be triggered via normal compilation, renamed them under `rust_*` and exported via `EXPORT_SYMBOL` instead. * Added a prelude. + A `shlex` package which serves as an example of an "inline" dependency (i.e. package checked out copy to avoid the network) - The example driver was setup at `drivers/char/rust_example/`. - Misc + The beginning of `Documentation/rust/` with a quick start guide. + `MAINTAINERS` entry. + SPDXs for all files. Other notes that aren't in `TODO`s:   - We could minimize the network requirements (use `bindgen` binary, use more inline dependencies...), but it is not clear it would be a good idea for the moment due to `core`/`alloc`/`compiler-builtins`. - The intention of `rust/` is to have a place to put extra dependencies and split the `kernel` package into several in the future if it grows. It could resemble the usual kernel tree structure. - With several drivers being built-in, `cargo` recompiles `kernel` and triggers duplicate symbol errors when merging thin archives. We need to make it only compile `kernel` once.   - When the above works, then `make`'s `-j` calling concurrent `cargo`s (e.g. several drivers at the same time) should be OK, I think. According to https://github.com/rust-lang/cargo/pull/2486 it shouldn't miscompile anything, but if they start locking on each other we will have make jobs waiting that could have been doing something else. Signed-off-by: Miguel Ojeda --- .gitignore | 3 + Cargo.lock | 374 +++++++++++++++++++++++++++++++++++ Cargo.toml | 10 + Documentation/index.rst | 1 + Documentation/rust/index.rst | 16 ++ Documentation/rust/quick-start.rst | 47 +++++ MAINTAINERS | 9 + Makefile | 62 +++++- arch/x86/Kconfig | 2 +- drivers/char/Kconfig | 12 ++ drivers/char/Makefile | 2 + drivers/char/rust_example/Cargo.toml | 15 ++ drivers/char/rust_example/Makefile | 3 + drivers/char/rust_example/src/lib.rs | 33 ++++ include/linux/kallsyms.h | 2 +- init/Kconfig | 13 ++ kernel/livepatch/core.c | 4 +- rust/Makefile | 4 + rust/helpers.c | 21 ++ rust/kernel/Cargo.toml | 15 ++ rust/kernel/build.rs | 133 +++++++++++++ rust/kernel/src/.gitignore | 3 + rust/kernel/src/allocator.rs | 26 +++ rust/kernel/src/bindings.rs | 16 ++ rust/kernel/src/bindings_helper.h | 12 ++ rust/kernel/src/c_types.rs | 25 +++ rust/kernel/src/chrdev.rs | 101 ++++++++++ rust/kernel/src/error.rs | 32 +++ rust/kernel/src/file_operations.rs | 219 ++++++++++++++++++++ rust/kernel/src/filesystem.rs | 82 ++++++++ rust/kernel/src/lib.rs | 161 +++++++++++++++ rust/kernel/src/prelude.rs | 16 ++ rust/kernel/src/printk.rs | 72 +++++++ rust/kernel/src/random.rs | 43 ++++ rust/kernel/src/sysctl.rs | 175 ++++++++++++++++ rust/kernel/src/types.rs | 54 +++++ rust/kernel/src/user_ptr.rs | 176 +++++++++++++++++ rust/shlex/Cargo.toml | 11 ++ rust/shlex/src/lib.rs | 231 ++++++++++++++++++++++ scripts/Makefile.build | 18 ++ scripts/Makefile.lib | 37 +++- scripts/kallsyms.c | 2 +- scripts/rust-version.sh | 31 +++ tools/include/linux/kallsyms.h | 2 +- tools/include/linux/lockdep.h | 2 +- 45 files changed, 2314 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Documentation/rust/index.rst create mode 100644 Documentation/rust/quick-start.rst create mode 100644 drivers/char/rust_example/Cargo.toml create mode 100644 drivers/char/rust_example/Makefile create mode 100644 drivers/char/rust_example/src/lib.rs create mode 100644 rust/Makefile create mode 100644 rust/helpers.c create mode 100644 rust/kernel/Cargo.toml create mode 100644 rust/kernel/build.rs create mode 100644 rust/kernel/src/.gitignore create mode 100644 rust/kernel/src/allocator.rs create mode 100644 rust/kernel/src/bindings.rs create mode 100644 rust/kernel/src/bindings_helper.h create mode 100644 rust/kernel/src/c_types.rs create mode 100644 rust/kernel/src/chrdev.rs create mode 100644 rust/kernel/src/error.rs create mode 100644 rust/kernel/src/file_operations.rs create mode 100644 rust/kernel/src/filesystem.rs create mode 100644 rust/kernel/src/lib.rs create mode 100644 rust/kernel/src/prelude.rs create mode 100644 rust/kernel/src/printk.rs create mode 100644 rust/kernel/src/random.rs create mode 100644 rust/kernel/src/sysctl.rs create mode 100644 rust/kernel/src/types.rs create mode 100644 rust/kernel/src/user_ptr.rs create mode 100644 rust/shlex/Cargo.toml create mode 100644 rust/shlex/src/lib.rs create mode 100755 scripts/rust-version.sh diff --git a/.gitignore b/.gitignore index 162bd2b67bdf..0df79f9636ff 100644 --- a/.gitignore +++ b/.gitignore @@ -152,3 +152,6 @@ x509.genkey # Clang's compilation database file /compile_commands.json + +# Rust (cargo) compilation artifacts +/target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000000..414ef254f79e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,374 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bindgen" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "which", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "bindgen", + "bitflags", + "shlex 0.1.1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "rust_example" +version = "0.1.0" +dependencies = [ + "kernel", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "0.1.1" + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000000..26c9c7516ea0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +# TODO: generate automatically +[workspace] +members = [ + "rust/shlex", + "rust/kernel", + "drivers/char/rust_example", +] + diff --git a/Documentation/index.rst b/Documentation/index.rst index 57719744774c..3a9ab325b9c5 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -82,6 +82,7 @@ merged much easier. maintainer/index fault-injection/index livepatch/index + rust/index Kernel API documentation diff --git a/Documentation/rust/index.rst b/Documentation/rust/index.rst new file mode 100644 index 000000000000..bcb4de39f3f1 --- /dev/null +++ b/Documentation/rust/index.rst @@ -0,0 +1,16 @@ +Rust +==== + +Documentation related to Rust within the kernel. If you are starting out, read the :ref:`Documentation/rust/quick-start.rst ` guide. + +.. toctree:: + :maxdepth: 1 + + quick-start + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst new file mode 100644 index 000000000000..b88da6a9f9b4 --- /dev/null +++ b/Documentation/rust/quick-start.rst @@ -0,0 +1,47 @@ +.. _rust_quick_start: + +Quick Start +=========== + +This document describes how to get started with Rust kernel development. + +Requirements +------------ + +A nightly Rust toolchain (at least ``rustc``, ``cargo`` and ``rustfmt``) is required. In the future, this restriction will be lifted. If you are using ``rustup``, run:: + + rustup toolchain install nightly + +Otherwise, fetch a standalone installer from: + + https://www.rust-lang.org + +The sources for the compiler are required to be available. If you are using ``rustup``, run:: + + rustup component add rust-src + +Otherwise, if you used a standalone installer, you can clone the Rust compiler sources and create a symlink to them in the installation folder of your nightly toolchain:: + + git clone https://github.com/rust-lang/rust + ln -s rust .../rust-nightly/lib/rustlib/src + +Testing a simple driver +----------------------- + +If the kernel configuration system is able to find ``rustc`` and ``cargo``, it will enable Rust support (``CONFIG_HAS_RUST``). In turn, this will make visible the rest of options that depend on Rust. + +A simple driver you can compile to test things out is at ``drivers/char/rust_example`` (``CONFIG_RUST_EXAMPLE``). Enable it and compile the kernel with: + + make LLVM=1 + +TODO: drop LLVM=1, allowing to mix GCC with LLVM + +Avoiding the network +-------------------- + +You can prefetch the dependencies that ``cargo`` will download by running:: + + cargo fetch + +in the kernel sources root. After this step, a network connection is not needed anymore. + diff --git a/MAINTAINERS b/MAINTAINERS index f0068bceeb61..44564ec6f2a3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15024,6 +15024,15 @@ L: linux-rdma@vger.kernel.org S: Maintained F: drivers/infiniband/ulp/rtrs/ +RUST +W: https://github.com/Rust-for-Linux +S: Maintained +K: \b(?i:rust)\b +L: linux-rust@vger.kernel.org (TODO: waiting for reply to my request) +F: rust/ +F: Documentation/rust/ +F: drivers/char/rust_example/ + RXRPC SOCKETS (AF_RXRPC) M: David Howells L: linux-afs@lists.infradead.org diff --git a/Makefile b/Makefile index f21168154160..997996f765f9 100644 --- a/Makefile +++ b/Makefile @@ -86,9 +86,11 @@ endif ifeq ($(KBUILD_VERBOSE),1) quiet = Q = + CARGO_VERBOSE = --verbose else quiet=quiet_ Q = @ + CARGO_VERBOSE = # TODO: could be --quiet if needed endif # If the user is running make -s (silent mode), suppress echoing of @@ -446,6 +448,8 @@ READELF = $(CROSS_COMPILE)readelf OBJSIZE = $(CROSS_COMPILE)size STRIP = $(CROSS_COMPILE)strip endif +RUSTC = rustc +CARGO = cargo PAHOLE = pahole RESOLVE_BTFIDS = $(objtree)/tools/bpf/resolve_btfids/resolve_btfids LEX = flex @@ -470,9 +474,13 @@ CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF) NOSTDINC_FLAGS := CFLAGS_MODULE = +RUSTCFLAGS_MODULE = +CARGOFLAGS_MODULE = AFLAGS_MODULE = LDFLAGS_MODULE = CFLAGS_KERNEL = +RUSTCFLAGS_KERNEL = +CARGOFLAGS_KERNEL = AFLAGS_KERNEL = LDFLAGS_vmlinux = @@ -500,16 +508,27 @@ KBUILD_CFLAGS := -Wall -Wundef -Werror=strict-prototypes -Wno-trigraphs \ -Wno-format-security \ -std=gnu89 KBUILD_CPPFLAGS := -D__KERNEL__ +KBUILD_RUSTCFLAGS := +# TODO: a simple way to update `Cargo.lock` when we add a new driver +# TODO: another option is using explicit target specs, e.g. +# `--target=$(srctree)/arch/$(SRCARCH)/rust-target-spec.json` +KBUILD_CARGOFLAGS := $(CARGO_VERBOSE) --locked \ + -Z build-std=core,alloc -Z unstable-options \ + --out-dir=out --target=x86_64-linux-kernel KBUILD_AFLAGS_KERNEL := KBUILD_CFLAGS_KERNEL := +KBUILD_RUSTCFLAGS_KERNEL := +KBUILD_CARGOFLAGS_KERNEL := KBUILD_AFLAGS_MODULE := -DMODULE KBUILD_CFLAGS_MODULE := -DMODULE +KBUILD_RUSTCFLAGS_MODULE := --cfg module +KBUILD_CARGOFLAGS_MODULE := KBUILD_LDFLAGS_MODULE := export KBUILD_LDS_MODULE := $(srctree)/scripts/module-common.lds KBUILD_LDFLAGS := CLANG_FLAGS := -export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC +export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC RUSTC CARGO export CPP AR NM STRIP OBJCOPY OBJDUMP OBJSIZE READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL export PERL PYTHON PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD @@ -518,9 +537,11 @@ export KBUILD_HOSTCXXFLAGS KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS LDFLAGS_MODULE export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGS export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE export CFLAGS_KASAN CFLAGS_KASAN_NOSANITIZE CFLAGS_UBSAN CFLAGS_KCSAN +export KBUILD_RUSTCFLAGS RUSTCFLAGS_KERNEL RUSTCFLAGS_MODULE +export KBUILD_CARGOFLAGS CARGOFLAGS_KERNEL CARGOFLAGS_MODULE export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE -export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE -export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL +export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_RUSTCFLAGS_MODULE KBUILD_CARGOFLAGS_MODULE KBUILD_LDFLAGS_MODULE +export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL KBUILD_RUSTCFLAGS_KERNEL KBUILD_CARGOFLAGS_KERNEL # Files to ignore in find ... statements @@ -733,12 +754,21 @@ KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation) KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow) KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) +# TODO: perhaps we could have some independent options for Rust only +KBUILD_CARGOFLAGS += --release + ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE KBUILD_CFLAGS += -O2 +KBUILD_RUSTCFLAGS += # TODO: do we want -C opt-level=2 here? +KBUILD_CARGOFLAGS += else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 KBUILD_CFLAGS += -O3 +KBUILD_RUSTCFLAGS += # flags already passed by cargo's --release +KBUILD_CARGOFLAGS += else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE KBUILD_CFLAGS += -Os +KBUILD_RUSTCFLAGS += -C opt-level=z +KBUILD_CARGOFLAGS += endif # Tell gcc to never replace conditional load with a non-conditional one @@ -814,6 +844,10 @@ KBUILD_CFLAGS += -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-f endif DEBUG_CFLAGS := $(call cc-option, -fno-var-tracking-assignments) +DEBUG_RUSTCFLAGS := +DEBUG_CARGOFLAGS := + +# TODO: see what we should set for rustc/cargo for debug ifdef CONFIG_DEBUG_INFO ifdef CONFIG_DEBUG_INFO_SPLIT @@ -841,6 +875,12 @@ endif KBUILD_CFLAGS += $(DEBUG_CFLAGS) export DEBUG_CFLAGS +KBUILD_RUSTCFLAGS += $(DEBUG_RUSTCFLAGS) +export DEBUG_RUSTCFLAGS + +KBUILD_CARGOFLAGS += $(DEBUG_CARGOFLAGS) +export DEBUG_CARGOFLAGS + ifdef CONFIG_FUNCTION_TRACER ifdef CONFIG_FTRACE_MCOUNT_RECORD # gcc 5 supports generating the mcount tables directly @@ -972,10 +1012,12 @@ include $(addprefix $(srctree)/, $(include-y)) # Do not add $(call cc-option,...) below this line. When you build the kernel # from the clean source tree, the GCC plugins do not exist at this point. -# Add user supplied CPPFLAGS, AFLAGS and CFLAGS as the last assignments +# Add user supplied CPPFLAGS, AFLAGS, CFLAGS, RUSTCFLAGS and CARGOFLAGS as the last assignments KBUILD_CPPFLAGS += $(KCPPFLAGS) KBUILD_AFLAGS += $(KAFLAGS) KBUILD_CFLAGS += $(KCFLAGS) +KBUILD_RUSTCFLAGS += $(KRUSTCFLAGS) +KBUILD_CARGOFLAGS += $(KCARGOFLAGS) KBUILD_LDFLAGS_MODULE += --build-id LDFLAGS_vmlinux += --build-id @@ -1100,6 +1142,10 @@ export MODULES_NSDEPS := $(extmod-prefix)modules.nsdeps ifeq ($(KBUILD_EXTMOD),) core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ +ifdef CONFIG_HAS_RUST +core-y += rust/ +endif + vmlinux-dirs := $(patsubst %/,%,$(filter %/, \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(libs-y) $(libs-m))) @@ -1483,13 +1529,17 @@ DISTCLEAN_FILES += tags TAGS cscope* GPATH GTAGS GRTAGS GSYMS # clean: rm-files := $(CLEAN_FILES) -PHONY += archclean vmlinuxclean +PHONY += archclean vmlinuxclean cargoclean vmlinuxclean: $(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean $(Q)$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) clean) -clean: archclean vmlinuxclean +# TODO: clean particular subfolders via `Makefile.clean` +cargoclean: + $(Q)$(CARGO) clean + +clean: archclean vmlinuxclean cargoclean # mrproper - Delete all generated files, including .config # diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 7101ac64bb20..7b7d07d1eade 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -148,7 +148,7 @@ config X86 select HAVE_ARCH_MMAP_RND_BITS if MMU select HAVE_ARCH_MMAP_RND_COMPAT_BITS if MMU && COMPAT select HAVE_ARCH_COMPAT_MMAP_BASES if MMU && COMPAT - select HAVE_ARCH_PREL32_RELOCATIONS + #select HAVE_ARCH_PREL32_RELOCATIONS TODO: see the `kernel_module` macro select HAVE_ARCH_SECCOMP_FILTER select HAVE_ARCH_THREAD_STRUCT_WHITELIST select HAVE_ARCH_STACKLEAK diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index b1bd336761b1..e12a7608e43a 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -470,6 +470,18 @@ config ADI and SSM (Silicon Secured Memory). Intended consumers of this driver include crash and makedumpfile. +config RUST_EXAMPLE + depends on HAS_RUST + tristate "Rust Example" + default n + help + This option builds an example module written in Rust. + + To compile this as a module, choose M here: + the module will be called rust_example. + + If unsure, say N. + endmenu config RANDOM_TRUST_CPU diff --git a/drivers/char/Makefile b/drivers/char/Makefile index ffce287ef415..b254119cb97d 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -47,3 +47,5 @@ obj-$(CONFIG_PS3_FLASH) += ps3flash.o obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o obj-$(CONFIG_ADI) += adi.o + +obj-$(CONFIG_RUST_EXAMPLE) += rust_example/ diff --git a/drivers/char/rust_example/Cargo.toml b/drivers/char/rust_example/Cargo.toml new file mode 100644 index 000000000000..c4adcdf3458a --- /dev/null +++ b/drivers/char/rust_example/Cargo.toml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +# No need for details like `authors` here -- that goes in the modinfo +# via the `kernel_module!` macro +[package] +name = "rust_example" +version = "0.1.0" +edition = "2018" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +kernel = { path = "../../../rust/kernel" } + diff --git a/drivers/char/rust_example/Makefile b/drivers/char/rust_example/Makefile new file mode 100644 index 000000000000..f8847c8cad82 --- /dev/null +++ b/drivers/char/rust_example/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_RUST_EXAMPLE) += rust_example.o diff --git a/drivers/char/rust_example/src/lib.rs b/drivers/char/rust_example/src/lib.rs new file mode 100644 index 000000000000..9f542d6929db --- /dev/null +++ b/drivers/char/rust_example/src/lib.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![no_std] + +use kernel::prelude::*; + +struct RustExample { + message: String, +} + +impl KernelModule for RustExample { + fn init() -> KernelResult { + println!("Rust Example (init)"); + println!("Am I built-in? {}", !cfg!(module)); + Ok(RustExample { + message: "on the heap!".to_owned(), + }) + } +} + +impl Drop for RustExample { + fn drop(&mut self) { + println!("My message is {}", self.message); + println!("Rust Example (exit)"); + } +} + +kernel_module!( + RustExample, + author: b"Rust for Linux Contributors", + description: b"An example kernel module written in Rust", + license: b"GPL v2" +); diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h index 481273f0c72d..d26c41520ad5 100644 --- a/include/linux/kallsyms.h +++ b/include/linux/kallsyms.h @@ -14,7 +14,7 @@ #include -#define KSYM_NAME_LEN 128 +#define KSYM_NAME_LEN 256 #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s]") + (KSYM_NAME_LEN - 1) + \ 2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + 1) diff --git a/init/Kconfig b/init/Kconfig index d6a0b31b13dc..ab00f9ae81f3 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -47,6 +47,19 @@ config CLANG_VERSION int default $(shell,$(srctree)/scripts/clang-version.sh $(CC)) +config HAS_RUST + def_bool $(success,$(RUSTC) --version && $(CARGO) --version) + +config RUSTC_VERSION + depends on HAS_RUST + int + default $(shell,$(srctree)/scripts/rust-version.sh $(RUSTC)) + +config CARGO_VERSION + depends on HAS_RUST + int + default $(shell,$(srctree)/scripts/rust-version.sh $(CARGO)) + config CC_CAN_LINK bool default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(m64-flag)) if 64BIT diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index f76fdb925532..c51f2ca5094e 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -214,7 +214,7 @@ static int klp_resolve_symbols(Elf64_Shdr *sechdrs, const char *strtab, * we use the smallest/strictest upper bound possible (56, based on * the current definition of MODULE_NAME_LEN) to prevent overflows. */ - BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 128); + BUILD_BUG_ON(MODULE_NAME_LEN < 56 || KSYM_NAME_LEN != 256); relas = (Elf_Rela *) relasec->sh_addr; /* For each rela in this klp relocation section */ @@ -228,7 +228,7 @@ static int klp_resolve_symbols(Elf64_Shdr *sechdrs, const char *strtab, /* Format: .klp.sym.sym_objname.sym_name,sympos */ cnt = sscanf(strtab + sym->st_name, - ".klp.sym.%55[^.].%127[^,],%lu", + ".klp.sym.%55[^.].%255[^,],%lu", sym_objname, sym_name, &sympos); if (cnt != 3) { pr_err("symbol %s has an incorrectly formatted name\n", diff --git a/rust/Makefile b/rust/Makefile new file mode 100644 index 000000000000..02f2459083e4 --- /dev/null +++ b/rust/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_HAS_RUST) = helpers.o + diff --git a/rust/helpers.c b/rust/helpers.c new file mode 100644 index 000000000000..1efb4776b9e8 --- /dev/null +++ b/rust/helpers.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +void rust_helper_BUG(void) +{ + BUG(); +} +EXPORT_SYMBOL(rust_helper_BUG); + +int rust_helper_access_ok(const void __user *addr, unsigned long n) +{ + return access_ok(addr, n); +} +EXPORT_SYMBOL(rust_helper_access_ok); + +// See https://github.com/rust-lang/rust-bindgen/issues/1671 +static_assert(__builtin_types_compatible_p(size_t, uintptr_t), + "size_t must match uintptr_t, what architecture is this??"); diff --git a/rust/kernel/Cargo.toml b/rust/kernel/Cargo.toml new file mode 100644 index 000000000000..24ccb0912410 --- /dev/null +++ b/rust/kernel/Cargo.toml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +[package] +name = "kernel" +version = "0.1.0" +authors = ["Rust for Linux Contributors"] +edition = "2018" + +[dependencies] +bitflags = "1" + +[build-dependencies] +bindgen = "0.54" +shlex = { path = "../shlex" } + diff --git a/rust/kernel/build.rs b/rust/kernel/build.rs new file mode 100644 index 000000000000..b9085a6367b4 --- /dev/null +++ b/rust/kernel/build.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 + +use std::path::PathBuf; +use std::env; + +const INCLUDED_TYPES: &[&str] = &["file_system_type", "mode_t", "umode_t", "ctl_table"]; +const INCLUDED_FUNCTIONS: &[&str] = &[ + "cdev_add", + "cdev_init", + "cdev_del", + "register_filesystem", + "unregister_filesystem", + "krealloc", + "kfree", + "mount_nodev", + "kill_litter_super", + "register_sysctl", + "unregister_sysctl_table", + "access_ok", + "_copy_to_user", + "_copy_from_user", + "alloc_chrdev_region", + "unregister_chrdev_region", + "wait_for_random_bytes", + "get_random_bytes", + "rng_is_initialized", + "printk", + "add_device_randomness", +]; +const INCLUDED_VARS: &[&str] = &[ + "EINVAL", + "ENOMEM", + "ESPIPE", + "EFAULT", + "EAGAIN", + "__this_module", + "FS_REQUIRES_DEV", + "FS_BINARY_MOUNTDATA", + "FS_HAS_SUBTYPE", + "FS_USERNS_MOUNT", + "FS_RENAME_DOES_D_MOVE", + "BINDINGS_GFP_KERNEL", + "KERN_INFO", + "VERIFY_WRITE", + "LINUX_VERSION_CODE", + "SEEK_SET", + "SEEK_CUR", + "SEEK_END", + "O_NONBLOCK", +]; +const OPAQUE_TYPES: &[&str] = &[ + // These need to be opaque because they're both packed and aligned, which rustc + // doesn't support yet. See https://github.com/rust-lang/rust/issues/59154 + // and https://github.com/rust-lang/rust-bindgen/issues/1538 + "desc_struct", + "xregs_state", +]; + +// Takes the CFLAGS from the kernel Makefile and changes all the include paths to be absolute +// instead of relative. +fn prepare_cflags(cflags: &str, kernel_dir: &str) -> Vec { + let cflag_parts = shlex::split(&cflags).unwrap(); + let mut cflag_iter = cflag_parts.iter(); + let mut kernel_args = vec![]; + while let Some(arg) = cflag_iter.next() { + // TODO: bindgen complains + if arg.starts_with("-Wp,-MMD") { + continue; + } + + if arg.starts_with("-I") && !arg.starts_with("-I/") { + kernel_args.push(format!("-I{}/{}", kernel_dir, &arg[2..])); + } else if arg == "-include" { + kernel_args.push(arg.to_string()); + let include_path = cflag_iter.next().unwrap(); + if include_path.starts_with('/') { + kernel_args.push(include_path.to_string()); + } else { + kernel_args.push(format!("{}/{}", kernel_dir, include_path)); + } + } else { + kernel_args.push(arg.to_string()); + } + } + kernel_args +} + +fn main() { + println!("cargo:rerun-if-env-changed=CC"); + println!("cargo:rerun-if-env-changed=RUST_BINDGEN_CFLAGS"); + + let kernel_dir = "../../"; + let cflags = env::var("RUST_BINDGEN_CFLAGS") + .expect("Must be invoked from kernel makefile"); + + let kernel_args = prepare_cflags(&cflags, &kernel_dir); + + let target = env::var("TARGET").unwrap(); + + let mut builder = bindgen::Builder::default() + .use_core() + .ctypes_prefix("c_types") + .derive_default(true) + .size_t_is_usize(true) + .rustfmt_bindings(true); + + builder = builder.clang_arg(format!("--target={}", target)); + for arg in kernel_args.iter() { + builder = builder.clang_arg(arg.clone()); + } + + println!("cargo:rerun-if-changed=src/bindings_helper.h"); + builder = builder.header("src/bindings_helper.h"); + + for t in INCLUDED_TYPES { + builder = builder.whitelist_type(t); + } + for f in INCLUDED_FUNCTIONS { + builder = builder.whitelist_function(f); + } + for v in INCLUDED_VARS { + builder = builder.whitelist_var(v); + } + for t in OPAQUE_TYPES { + builder = builder.opaque_type(t); + } + let bindings = builder.generate().expect("Unable to generate bindings"); + + let out_path = PathBuf::from("src/bindings_gen.rs"); + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} diff --git a/rust/kernel/src/.gitignore b/rust/kernel/src/.gitignore new file mode 100644 index 000000000000..61552b03b12d --- /dev/null +++ b/rust/kernel/src/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +bindings_gen.rs diff --git a/rust/kernel/src/allocator.rs b/rust/kernel/src/allocator.rs new file mode 100644 index 000000000000..27647be92b51 --- /dev/null +++ b/rust/kernel/src/allocator.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr; + +use crate::bindings; +use crate::c_types; + +pub struct KernelAllocator; + +unsafe impl GlobalAlloc for KernelAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // krealloc is used instead of kmalloc because kmalloc is an inline function and can't be + // bound to as a result + bindings::krealloc(ptr::null(), layout.size(), bindings::GFP_KERNEL) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + bindings::kfree(ptr as *const c_types::c_void); + } +} + +#[alloc_error_handler] +fn oom(_layout: Layout) -> ! { + panic!("Out of memory!"); +} diff --git a/rust/kernel/src/bindings.rs b/rust/kernel/src/bindings.rs new file mode 100644 index 000000000000..cfb004a6d786 --- /dev/null +++ b/rust/kernel/src/bindings.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +#[allow( + clippy::all, + non_camel_case_types, + non_upper_case_globals, + non_snake_case, + improper_ctypes +)] +mod bindings_raw { + use crate::c_types; + include!("bindings_gen.rs"); +} +pub use bindings_raw::*; + +pub const GFP_KERNEL: gfp_t = BINDINGS_GFP_KERNEL; diff --git a/rust/kernel/src/bindings_helper.h b/rust/kernel/src/bindings_helper.h new file mode 100644 index 000000000000..b5b0b2e1d18d --- /dev/null +++ b/rust/kernel/src/bindings_helper.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include +#include +#include +#include +#include +#include +#include + +// bindgen gets confused at certain things +const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; diff --git a/rust/kernel/src/c_types.rs b/rust/kernel/src/c_types.rs new file mode 100644 index 000000000000..35776920c99d --- /dev/null +++ b/rust/kernel/src/c_types.rs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#![allow(non_camel_case_types)] + +#[cfg(target_arch = "x86_64")] +mod c { + use core::ffi; + + pub type c_int = i32; + pub type c_char = i8; + pub type c_long = i64; + pub type c_longlong = i64; + pub type c_short = i16; + pub type c_uchar = u8; + pub type c_uint = u32; + pub type c_ulong = u64; + pub type c_ulonglong = u64; + pub type c_ushort = u16; + pub type c_schar = i8; + pub type c_size_t = usize; + pub type c_ssize_t = isize; + pub type c_void = ffi::c_void; +} + +pub use c::*; diff --git a/rust/kernel/src/chrdev.rs b/rust/kernel/src/chrdev.rs new file mode 100644 index 000000000000..4606a7bd5a73 --- /dev/null +++ b/rust/kernel/src/chrdev.rs @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::TryInto; +use core::mem; +use core::ops::Range; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + +use crate::bindings; +use crate::c_types; +use crate::error::{Error, KernelResult}; +use crate::file_operations; +use crate::types::CStr; + +pub fn builder(name: &'static CStr, minors: Range) -> KernelResult { + Ok(Builder { + name, + minors, + file_ops: vec![], + }) +} + +pub struct Builder { + name: &'static CStr, + minors: Range, + file_ops: Vec<&'static bindings::file_operations>, +} + +impl Builder { + pub fn register_device(mut self) -> Builder { + if self.file_ops.len() >= self.minors.len() { + panic!("More devices registered than minor numbers allocated.") + } + self.file_ops + .push(&file_operations::FileOperationsVtable::::VTABLE); + self + } + + pub fn build(self) -> KernelResult { + let mut dev: bindings::dev_t = 0; + let res = unsafe { + bindings::alloc_chrdev_region( + &mut dev, + self.minors.start.into(), + self.minors.len().try_into()?, + self.name.as_ptr() as *const c_types::c_char, + ) + }; + if res != 0 { + return Err(Error::from_kernel_errno(res)); + } + + // Turn this into a boxed slice immediately because the kernel stores pointers into it, and + // so that data should never be moved. + let mut cdevs = vec![unsafe { mem::zeroed() }; self.file_ops.len()].into_boxed_slice(); + for (i, file_op) in self.file_ops.iter().enumerate() { + unsafe { + bindings::cdev_init(&mut cdevs[i], *file_op); + // TODO: proper `THIS_MODULE` handling + cdevs[i].owner = core::ptr::null_mut(); + let rc = bindings::cdev_add(&mut cdevs[i], dev + i as bindings::dev_t, 1); + if rc != 0 { + // Clean up the ones that were allocated. + for j in 0..=i { + bindings::cdev_del(&mut cdevs[j]); + } + bindings::unregister_chrdev_region(dev, self.minors.len() as _); + return Err(Error::from_kernel_errno(rc)); + } + } + } + + Ok(Registration { + dev, + count: self.minors.len(), + cdevs, + }) + } +} + +pub struct Registration { + dev: bindings::dev_t, + count: usize, + cdevs: Box<[bindings::cdev]>, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl Sync for Registration {} + +impl Drop for Registration { + fn drop(&mut self) { + unsafe { + for dev in self.cdevs.iter_mut() { + bindings::cdev_del(dev); + } + bindings::unregister_chrdev_region(self.dev, self.count as _); + } + } +} diff --git a/rust/kernel/src/error.rs b/rust/kernel/src/error.rs new file mode 100644 index 000000000000..95e322e39a88 --- /dev/null +++ b/rust/kernel/src/error.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::num::TryFromIntError; + +use crate::bindings; +use crate::c_types; + +pub struct Error(c_types::c_int); + +impl Error { + pub const EINVAL: Self = Error(-(bindings::EINVAL as i32)); + pub const ENOMEM: Self = Error(-(bindings::ENOMEM as i32)); + pub const EFAULT: Self = Error(-(bindings::EFAULT as i32)); + pub const ESPIPE: Self = Error(-(bindings::ESPIPE as i32)); + pub const EAGAIN: Self = Error(-(bindings::EAGAIN as i32)); + + pub fn from_kernel_errno(errno: c_types::c_int) -> Error { + Error(errno) + } + + pub fn to_kernel_errno(&self) -> c_types::c_int { + self.0 + } +} + +impl From for Error { + fn from(_: TryFromIntError) -> Error { + Error::EINVAL + } +} + +pub type KernelResult = Result; diff --git a/rust/kernel/src/file_operations.rs b/rust/kernel/src/file_operations.rs new file mode 100644 index 000000000000..100fb62281e9 --- /dev/null +++ b/rust/kernel/src/file_operations.rs @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::{TryFrom, TryInto}; +use core::{marker, mem, ptr}; + +use alloc::boxed::Box; + +use crate::bindings; +use crate::c_types; +use crate::error::{Error, KernelResult}; +use crate::user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter}; + +bitflags::bitflags! { + pub struct FileFlags: c_types::c_uint { + const NONBLOCK = bindings::O_NONBLOCK; + } +} + +pub struct File { + ptr: *const bindings::file, +} + +impl File { + unsafe fn from_ptr(ptr: *const bindings::file) -> File { + File { ptr } + } + + pub fn pos(&self) -> u64 { + unsafe { (*self.ptr).f_pos as u64 } + } + + pub fn flags(&self) -> FileFlags { + FileFlags::from_bits_truncate(unsafe { (*self.ptr).f_flags }) + } +} + +// Matches std::io::SeekFrom in the Rust stdlib +pub enum SeekFrom { + Start(u64), + End(i64), + Current(i64), +} + +unsafe extern "C" fn open_callback( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_types::c_int { + let f = match T::open() { + Ok(f) => Box::new(f), + Err(e) => return e.to_kernel_errno(), + }; + (*file).private_data = Box::into_raw(f) as *mut c_types::c_void; + 0 +} + +unsafe extern "C" fn read_callback( + file: *mut bindings::file, + buf: *mut c_types::c_char, + len: c_types::c_size_t, + offset: *mut bindings::loff_t, +) -> c_types::c_ssize_t { + let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) { + Ok(ptr) => ptr.writer(), + Err(e) => return e.to_kernel_errno().try_into().unwrap(), + }; + let f = &*((*file).private_data as *const T); + // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63). + // See discussion in #113 + let positive_offset = match (*offset).try_into() { + Ok(v) => v, + Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(), + }; + let read = T::READ.unwrap(); + match read(f, &File::from_ptr(file), &mut data, positive_offset) { + Ok(()) => { + let written = len - data.len(); + (*offset) += bindings::loff_t::try_from(written).unwrap(); + written.try_into().unwrap() + } + Err(e) => e.to_kernel_errno().try_into().unwrap(), + } +} + +unsafe extern "C" fn write_callback( + file: *mut bindings::file, + buf: *const c_types::c_char, + len: c_types::c_size_t, + offset: *mut bindings::loff_t, +) -> c_types::c_ssize_t { + let mut data = match UserSlicePtr::new(buf as *mut c_types::c_void, len) { + Ok(ptr) => ptr.reader(), + Err(e) => return e.to_kernel_errno().try_into().unwrap(), + }; + let f = &*((*file).private_data as *const T); + // No FMODE_UNSIGNED_OFFSET support, so offset must be in [0, 2^63). + // See discussion in #113 + let positive_offset = match (*offset).try_into() { + Ok(v) => v, + Err(_) => return Error::EINVAL.to_kernel_errno().try_into().unwrap(), + }; + let write = T::WRITE.unwrap(); + match write(f, &mut data, positive_offset) { + Ok(()) => { + let read = len - data.len(); + (*offset) += bindings::loff_t::try_from(read).unwrap(); + read.try_into().unwrap() + } + Err(e) => e.to_kernel_errno().try_into().unwrap(), + } +} + +unsafe extern "C" fn release_callback( + _inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_types::c_int { + let ptr = mem::replace(&mut (*file).private_data, ptr::null_mut()); + drop(Box::from_raw(ptr as *mut T)); + 0 +} + +unsafe extern "C" fn llseek_callback( + file: *mut bindings::file, + offset: bindings::loff_t, + whence: c_types::c_int, +) -> bindings::loff_t { + let off = match whence as u32 { + bindings::SEEK_SET => match offset.try_into() { + Ok(v) => SeekFrom::Start(v), + Err(_) => return Error::EINVAL.to_kernel_errno().into(), + }, + bindings::SEEK_CUR => SeekFrom::Current(offset), + bindings::SEEK_END => SeekFrom::End(offset), + _ => return Error::EINVAL.to_kernel_errno().into(), + }; + let f = &*((*file).private_data as *const T); + let seek = T::SEEK.unwrap(); + match seek(f, &File::from_ptr(file), off) { + Ok(off) => off as bindings::loff_t, + Err(e) => e.to_kernel_errno().into(), + } +} + +pub(crate) struct FileOperationsVtable(marker::PhantomData); + +impl FileOperationsVtable { + pub(crate) const VTABLE: bindings::file_operations = bindings::file_operations { + open: Some(open_callback::), + release: Some(release_callback::), + read: if let Some(_) = T::READ { + Some(read_callback::) + } else { + None + }, + write: if let Some(_) = T::WRITE { + Some(write_callback::) + } else { + None + }, + llseek: if let Some(_) = T::SEEK { + Some(llseek_callback::) + } else { + None + }, + + check_flags: None, + compat_ioctl: None, + copy_file_range: None, + fallocate: None, + fadvise: None, + fasync: None, + flock: None, + flush: None, + fsync: None, + get_unmapped_area: None, + iterate: None, + iterate_shared: None, + iopoll: None, + lock: None, + mmap: None, + mmap_supported_flags: 0, + owner: ptr::null_mut(), + poll: None, + read_iter: None, + remap_file_range: None, + sendpage: None, + setlease: None, + show_fdinfo: None, + splice_read: None, + splice_write: None, + unlocked_ioctl: None, + write_iter: None, + }; +} + +pub type ReadFn = Option KernelResult<()>>; +pub type WriteFn = Option KernelResult<()>>; +pub type SeekFn = Option KernelResult>; + +/// `FileOperations` corresponds to the kernel's `struct file_operations`. You +/// implement this trait whenever you'd create a `struct file_operations`. +/// File descriptors may be used from multiple threads (or processes) +/// concurrently, so your type must be `Sync`. +pub trait FileOperations: Sync + Sized { + /// Creates a new instance of this file. Corresponds to the `open` function + /// pointer in `struct file_operations`. + fn open() -> KernelResult; + + /// Reads data from this file to userspace. Corresponds to the `read` + /// function pointer in `struct file_operations`. + const READ: ReadFn = None; + + /// Writes data from userspace o this file. Corresponds to the `write` + /// function pointer in `struct file_operations`. + const WRITE: WriteFn = None; + + /// Changes the position of the file. Corresponds to the `llseek` function + /// pointer in `struct file_operations`. + const SEEK: SeekFn = None; +} diff --git a/rust/kernel/src/filesystem.rs b/rust/kernel/src/filesystem.rs new file mode 100644 index 000000000000..11b415008f68 --- /dev/null +++ b/rust/kernel/src/filesystem.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 + +use alloc::boxed::Box; +use core::default::Default; +use core::marker; + +use crate::bindings; +use crate::c_types; +use crate::error; +use crate::types::CStr; + +pub struct Registration { + _phantom: marker::PhantomData, + ptr: Box, +} + +// This is safe because Registration doesn't actually expose any methods. +unsafe impl Sync for Registration where T: FileSystem {} + +impl Drop for Registration { + fn drop(&mut self) { + unsafe { bindings::unregister_filesystem(&mut *self.ptr) }; + } +} + +pub trait FileSystem: Sync { + const NAME: &'static CStr; + const FLAGS: FileSystemFlags; +} + +bitflags::bitflags! { + pub struct FileSystemFlags: c_types::c_int { + const REQUIRES_DEV = bindings::FS_REQUIRES_DEV as c_types::c_int; + const BINARY_MOUNTDATA = bindings::FS_BINARY_MOUNTDATA as c_types::c_int; + const HAS_SUBTYPE = bindings::FS_HAS_SUBTYPE as c_types::c_int; + const USERNS_MOUNT = bindings::FS_USERNS_MOUNT as c_types::c_int; + const RENAME_DOES_D_MOVE = bindings::FS_RENAME_DOES_D_MOVE as c_types::c_int; + } +} + +extern "C" fn fill_super_callback( + _sb: *mut bindings::super_block, + _data: *mut c_types::c_void, + _silent: c_types::c_int, +) -> c_types::c_int { + // T::fill_super(...) + // This should actually create an object that gets dropped by + // file_system_registration::kill_sb. You can point to it with + // sb->s_fs_info. + unimplemented!(); +} + +extern "C" fn mount_callback( + fs_type: *mut bindings::file_system_type, + flags: c_types::c_int, + _dev_name: *const c_types::c_char, + data: *mut c_types::c_void, +) -> *mut bindings::dentry { + unsafe { bindings::mount_nodev(fs_type, flags, data, Some(fill_super_callback::)) } +} + +pub fn register() -> error::KernelResult> { + let mut fs_registration = Registration { + ptr: Box::new(bindings::file_system_type { + name: T::NAME.as_ptr() as *const i8, + // TODO: proper `THIS_MODULE` handling + owner: core::ptr::null_mut(), + fs_flags: T::FLAGS.bits(), + mount: Some(mount_callback::), + kill_sb: Some(bindings::kill_litter_super), + + ..Default::default() + }), + _phantom: marker::PhantomData, + }; + let result = unsafe { bindings::register_filesystem(&mut *fs_registration.ptr) }; + if result != 0 { + return Err(error::Error::from_kernel_errno(result)); + } + + Ok(fs_registration) +} diff --git a/rust/kernel/src/lib.rs b/rust/kernel/src/lib.rs new file mode 100644 index 000000000000..bdb070cca953 --- /dev/null +++ b/rust/kernel/src/lib.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The `kernel` crate + +#![no_std] +#![feature(allocator_api, alloc_error_handler, const_raw_ptr_deref)] + +extern crate alloc; + +use core::panic::PanicInfo; + +mod allocator; +pub mod bindings; +pub mod c_types; +pub mod chrdev; +mod error; +pub mod file_operations; +pub mod filesystem; +pub mod prelude; +pub mod printk; +pub mod random; +pub mod sysctl; +mod types; +pub mod user_ptr; + +pub use crate::error::{Error, KernelResult}; +pub use crate::types::{CStr, Mode}; + +/// Declares the entrypoint for a kernel module. The first argument should be a type which +/// implements the [`KernelModule`] trait. Also accepts various forms of kernel metadata. +/// +/// Example: +/// ```rust,no_run +/// use kernel::prelude::*; +/// +/// struct MyKernelModule; +/// impl KernelModule for MyKernelModule { +/// fn init() -> KernelResult { +/// Ok(MyKernelModule) +/// } +/// } +/// +/// kernel_module!( +/// MyKernelModule, +/// author: b"Rust for Linux Contributors", +/// description: b"My very own kernel module!", +/// license: b"GPL" +/// ); +#[macro_export] +macro_rules! kernel_module { + ($module:ty, $($name:ident : $value:expr),*) => { + static mut __MOD: Option<$module> = None; + + // TODO: find a proper way to emulate the C macro, including + // dealing with `HAVE_ARCH_PREL32_RELOCATIONS` + #[cfg(not(module))] + #[link_section = ".initcall6.init"] + #[used] + pub static __initcall: extern "C" fn() -> $crate::c_types::c_int = init_module; + + #[no_mangle] + pub extern "C" fn init_module() -> $crate::c_types::c_int { + match <$module as $crate::KernelModule>::init() { + Ok(m) => { + unsafe { + __MOD = Some(m); + } + return 0; + } + Err(e) => { + return e.to_kernel_errno(); + } + } + } + + #[no_mangle] + pub extern "C" fn cleanup_module() { + unsafe { + // Invokes drop() on __MOD, which should be used for cleanup. + __MOD = None; + } + } + + $( + $crate::kernel_module!(@attribute $name, $value); + )* + }; + + // TODO: The modinfo attributes below depend on the compiler placing + // the variables in order in the .modinfo section, so that you end up + // with b"key=value\0" in order in the section. This is a reasonably + // standard trick in C, but I'm not sure that rustc guarantees it. + // + // Ideally we'd be able to use concat_bytes! + stringify_bytes! + + // some way of turning a string literal (or at least a string + // literal token) into a bytes literal, and get a single static + // [u8; * N] with the whole thing, but those don't really exist yet. + // Most of the alternatives (e.g. .as_bytes() as a const fn) give + // you a pointer, not an array, which isn't right. + + // TODO: `modules.builtin.modinfo` etc. is missing the prefix (module name) + (@attribute author, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_KEY: [u8; 7] = *b"author="; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static AUTHOR_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute description, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_KEY: [u8; 12] = *b"description="; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static DESCRIPTION_NUL: [u8; 1] = *b"\0"; + }; + + (@attribute license, $value:expr) => { + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_KEY: [u8; 8] = *b"license="; + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_VALUE: [u8; $value.len()] = *$value; + #[link_section = ".modinfo"] + #[used] + pub static LICENSE_NUL: [u8; 1] = *b"\0"; + }; +} + +/// KernelModule is the top level entrypoint to implementing a kernel module. Your kernel module +/// should implement the `init` method on it, which maps to the `module_init` macro in Linux C API. +/// You can use this method to do whatever setup or registration your module should do. For any +/// teardown or cleanup operations, your type may implement [`Drop`]. +/// +/// [`Drop`]: https://doc.rust-lang.org/stable/core/ops/trait.Drop.html +pub trait KernelModule: Sized + Sync { + fn init() -> KernelResult; +} + +extern "C" { + fn rust_helper_BUG() -> !; +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + unsafe { + rust_helper_BUG(); + } +} + +#[global_allocator] +static ALLOCATOR: allocator::KernelAllocator = allocator::KernelAllocator; diff --git a/rust/kernel/src/prelude.rs b/rust/kernel/src/prelude.rs new file mode 100644 index 000000000000..d88baa99c902 --- /dev/null +++ b/rust/kernel/src/prelude.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! The `kernel` prelude + +pub use alloc::{ + string::String, + borrow::ToOwned, +}; + +pub use super::{ + kernel_module, + println, + KernelResult, + KernelModule, +}; + diff --git a/rust/kernel/src/printk.rs b/rust/kernel/src/printk.rs new file mode 100644 index 000000000000..1a27c034258a --- /dev/null +++ b/rust/kernel/src/printk.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::cmp; +use core::fmt; + +use crate::bindings; +use crate::c_types::c_int; + +#[doc(hidden)] +pub fn printk(s: &[u8]) { + // Don't copy the trailing NUL from `KERN_INFO`. + let mut fmt_str = [0; bindings::KERN_INFO.len() - 1 + b"%.*s\0".len()]; + fmt_str[..bindings::KERN_INFO.len() - 1] + .copy_from_slice(&bindings::KERN_INFO[..bindings::KERN_INFO.len() - 1]); + fmt_str[bindings::KERN_INFO.len() - 1..].copy_from_slice(b"%.*s\0"); + + // TODO: I believe printk never fails + unsafe { bindings::printk(fmt_str.as_ptr() as _, s.len() as c_int, s.as_ptr()) }; +} + +// From kernel/print/printk.c +const LOG_LINE_MAX: usize = 1024 - 32; + +#[doc(hidden)] +pub struct LogLineWriter { + data: [u8; LOG_LINE_MAX], + pos: usize, +} + +#[allow(clippy::new_without_default)] +impl LogLineWriter { + pub fn new() -> LogLineWriter { + LogLineWriter { + data: [0u8; LOG_LINE_MAX], + pos: 0, + } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data[..self.pos] + } +} + +impl fmt::Write for LogLineWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + let copy_len = cmp::min(LOG_LINE_MAX - self.pos, s.as_bytes().len()); + self.data[self.pos..self.pos + copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); + self.pos += copy_len; + Ok(()) + } +} + +/// [`println!`] functions the same as it does in `std`, except instead of +/// printing to `stdout`, it writes to the kernel console at the `KERN_INFO` +/// level. +/// +/// [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html +#[macro_export] +macro_rules! println { + () => ({ + $crate::printk::printk("\n".as_bytes()); + }); + ($fmt:expr) => ({ + $crate::printk::printk(concat!($fmt, "\n").as_bytes()); + }); + ($fmt:expr, $($arg:tt)*) => ({ + use ::core::fmt; + let mut writer = $crate::printk::LogLineWriter::new(); + let _ = fmt::write(&mut writer, format_args!(concat!($fmt, "\n"), $($arg)*)).unwrap(); + $crate::printk::printk(writer.as_bytes()); + }); +} diff --git a/rust/kernel/src/random.rs b/rust/kernel/src/random.rs new file mode 100644 index 000000000000..bd80c835749d --- /dev/null +++ b/rust/kernel/src/random.rs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 + +use core::convert::TryInto; + +use crate::{bindings, c_types, error}; + +/// Fills `dest` with random bytes generated from the kernel's CSPRNG. Ensures +/// that the CSPRNG has been seeded before generating any random bytes, and +/// will block until it's ready. +pub fn getrandom(dest: &mut [u8]) -> error::KernelResult<()> { + let res = unsafe { bindings::wait_for_random_bytes() }; + if res != 0 { + return Err(error::Error::from_kernel_errno(res)); + } + + unsafe { + bindings::get_random_bytes( + dest.as_mut_ptr() as *mut c_types::c_void, + dest.len().try_into()?, + ); + } + Ok(()) +} + +/// Fills `dest` with random bytes generated from the kernel's CSPRNG. If the +/// CSPRNG is not yet seeded, returns an `Err(EAGAIN)` immediately. +pub fn getrandom_nonblock(dest: &mut [u8]) -> error::KernelResult<()> { + if !unsafe { bindings::rn