summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/.gitignore1
-rw-r--r--scripts/Kconfig.include6
-rw-r--r--scripts/Makefile3
-rw-r--r--scripts/Makefile.build60
-rw-r--r--scripts/Makefile.debug10
-rw-r--r--scripts/Makefile.host34
-rw-r--r--scripts/Makefile.lib12
-rw-r--r--scripts/Makefile.modfinal8
-rwxr-xr-xscripts/bpf_doc.py4
-rwxr-xr-xscripts/cc-version.sh12
-rwxr-xr-xscripts/checkpatch.pl8
-rwxr-xr-xscripts/decode_stacktrace.sh14
-rw-r--r--scripts/gcc-plugins/latent_entropy_plugin.c2
-rw-r--r--scripts/gcc-plugins/stackleak_plugin.c2
-rw-r--r--scripts/gcc-plugins/structleak_plugin.c2
-rwxr-xr-xscripts/generate_rust_analyzer.py134
-rw-r--r--scripts/generate_rust_target.rs227
-rwxr-xr-xscripts/is_rust_module.sh13
-rw-r--r--scripts/kallsyms.c47
-rw-r--r--scripts/kconfig/confdata.c75
-rwxr-xr-xscripts/kernel-doc82
-rwxr-xr-xscripts/min-tool-version.sh6
-rw-r--r--scripts/rust-is-available-bindgen-libclang.h2
-rwxr-xr-xscripts/rust-is-available.sh158
-rwxr-xr-xscripts/rustdoc_test_builder.py59
-rwxr-xr-xscripts/rustdoc_test_gen.py164
26 files changed, 1071 insertions, 74 deletions
diff --git a/scripts/.gitignore b/scripts/.gitignore
index eed308bef604..b7aec8eb1bd4 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
/asn1_compiler
/bin2c
+/generate_rust_target
/insert-sys-cert
/kallsyms
/module.lds
diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include
index 0496efd6e117..83e850321eb6 100644
--- a/scripts/Kconfig.include
+++ b/scripts/Kconfig.include
@@ -36,12 +36,12 @@ ld-option = $(success,$(LD) -v $(1))
as-instr = $(success,printf "%b\n" "$(1)" | $(CC) $(CLANG_FLAGS) -c -x assembler -o /dev/null -)
# check if $(CC) and $(LD) exist
-$(error-if,$(failure,command -v $(CC)),compiler '$(CC)' not found)
+$(error-if,$(failure,command -v $(CC)),C compiler '$(CC)' not found)
$(error-if,$(failure,command -v $(LD)),linker '$(LD)' not found)
-# Get the compiler name, version, and error out if it is not supported.
+# Get the C compiler name, version, and error out if it is not supported.
cc-info := $(shell,$(srctree)/scripts/cc-version.sh $(CC))
-$(error-if,$(success,test -z "$(cc-info)"),Sorry$(comma) this compiler is not supported.)
+$(error-if,$(success,test -z "$(cc-info)"),Sorry$(comma) this C compiler is not supported.)
cc-name := $(shell,set -- $(cc-info) && echo $1)
cc-version := $(shell,set -- $(cc-info) && echo $2)
diff --git a/scripts/Makefile b/scripts/Makefile
index f084f08ed176..1575af84d557 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -10,6 +10,9 @@ hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable
hostprogs-always-$(CONFIG_ASN1) += asn1_compiler
hostprogs-always-$(CONFIG_MODULE_SIG_FORMAT) += sign-file
hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert
+hostprogs-always-$(CONFIG_RUST) += generate_rust_target
+
+generate_rust_target-rust := y
HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include
HOSTLDLIBS_sorttable = -lpthread
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index cac070aee791..a71b69807c47 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -26,6 +26,7 @@ EXTRA_CPPFLAGS :=
EXTRA_LDFLAGS :=
asflags-y :=
ccflags-y :=
+rustflags-y :=
cppflags-y :=
ldflags-y :=
@@ -271,6 +272,65 @@ quiet_cmd_cc_lst_c = MKLST $@
$(obj)/%.lst: $(src)/%.c FORCE
$(call if_changed_dep,cc_lst_c)
+# Compile Rust sources (.rs)
+# ---------------------------------------------------------------------------
+
+rust_allowed_features := allocator_api,bench_black_box,concat_idents,generic_associated_types
+
+rust_common_cmd = \
+ RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \
+ -Zallow-features=$(rust_allowed_features) \
+ -Zcrate-attr=no_std \
+ -Zcrate-attr='feature($(rust_allowed_features))' \
+ --extern alloc --extern kernel \
+ --crate-type rlib --out-dir $(obj) -L $(objtree)/rust/ \
+ --crate-name $(basename $(notdir $@))
+
+rust_handle_depfile = \
+ mv $(obj)/$(basename $(notdir $@)).d $(depfile); \
+ sed -i '/^\#/d' $(depfile)
+
+# `--emit=obj`, `--emit=asm` and `--emit=llvm-ir` imply a single codegen unit
+# will be used. We explicitly request `-Ccodegen-units=1` in any case, and
+# the compiler shows a warning if it is not 1. However, if we ever stop
+# requesting it explicitly and we start using some other `--emit` that does not
+# imply it (and for which codegen is performed), then we would be out of sync,
+# i.e. the outputs we would get for the different single targets (e.g. `.ll`)
+# would not match each other.
+
+quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
+ cmd_rustc_o_rs = \
+ $(rust_common_cmd) --emit=dep-info,obj $<; \
+ $(rust_handle_depfile)
+
+$(obj)/%.o: $(src)/%.rs FORCE
+ $(call if_changed_dep,rustc_o_rs)
+
+quiet_cmd_rustc_i_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
+ cmd_rustc_i_rs = \
+ $(rust_common_cmd) --emit=dep-info -Zunpretty=expanded $< >$@; \
+ command -v $(RUSTFMT) >/dev/null && $(RUSTFMT) $@; \
+ $(rust_handle_depfile)
+
+$(obj)/%.i: $(src)/%.rs FORCE
+ $(call if_changed_dep,rustc_i_rs)
+
+quiet_cmd_rustc_s_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
+ cmd_rustc_s_rs = \
+ $(rust_common_cmd) --emit=dep-info,asm $<; \
+ $(rust_handle_depfile)
+
+$(obj)/%.s: $(src)/%.rs FORCE
+ $(call if_changed_dep,rustc_s_rs)
+
+quiet_cmd_rustc_ll_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
+ cmd_rustc_ll_rs = \
+ $(rust_common_cmd) --emit=dep-info,llvm-ir $<; \
+ $(rust_handle_depfile)
+
+$(obj)/%.ll: $(src)/%.rs FORCE
+ $(call if_changed_dep,rustc_ll_rs)
+
# Compile assembler sources (.S)
# ---------------------------------------------------------------------------
diff --git a/scripts/Makefile.debug b/scripts/Makefile.debug
index 9f39b0130551..fe87389d52c0 100644
--- a/scripts/Makefile.debug
+++ b/scripts/Makefile.debug
@@ -1,4 +1,5 @@
DEBUG_CFLAGS :=
+DEBUG_RUSTFLAGS :=
ifdef CONFIG_DEBUG_INFO_SPLIT
DEBUG_CFLAGS += -gsplit-dwarf
@@ -10,6 +11,12 @@ ifndef CONFIG_AS_IS_LLVM
KBUILD_AFLAGS += -Wa,-gdwarf-2
endif
+ifdef CONFIG_DEBUG_INFO_REDUCED
+DEBUG_RUSTFLAGS += -Cdebuginfo=1
+else
+DEBUG_RUSTFLAGS += -Cdebuginfo=2
+endif
+
ifndef CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT
dwarf-version-$(CONFIG_DEBUG_INFO_DWARF4) := 4
dwarf-version-$(CONFIG_DEBUG_INFO_DWARF5) := 5
@@ -31,3 +38,6 @@ endif
KBUILD_CFLAGS += $(DEBUG_CFLAGS)
export DEBUG_CFLAGS
+
+KBUILD_RUSTFLAGS += $(DEBUG_RUSTFLAGS)
+export DEBUG_RUSTFLAGS
diff --git a/scripts/Makefile.host b/scripts/Makefile.host
index 278b4d6ac945..da133780b751 100644
--- a/scripts/Makefile.host
+++ b/scripts/Makefile.host
@@ -22,6 +22,8 @@ $(obj)/%.tab.c $(obj)/%.tab.h: $(src)/%.y FORCE
# to preprocess a data file.
#
# Both C and C++ are supported, but preferred language is C for such utilities.
+# Rust is also supported, but it may only be used in scenarios where a Rust
+# toolchain is required to be available (e.g. when `CONFIG_RUST` is enabled).
#
# Sample syntax (see Documentation/kbuild/makefiles.rst for reference)
# hostprogs := bin2hex
@@ -37,15 +39,20 @@ $(obj)/%.tab.c $(obj)/%.tab.h: $(src)/%.y FORCE
# qconf-objs := menu.o
# Will compile qconf as a C++ program, and menu as a C program.
# They are linked as C++ code to the executable qconf
+#
+# hostprogs := target
+# target-rust := y
+# Will compile `target` as a Rust program, using `target.rs` as the crate root.
+# The crate may consist of several source files.
# C code
# Executables compiled from a single .c file
host-csingle := $(foreach m,$(hostprogs), \
- $(if $($(m)-objs)$($(m)-cxxobjs),,$(m)))
+ $(if $($(m)-objs)$($(m)-cxxobjs)$($(m)-rust),,$(m)))
# C executables linked based on several .o files
host-cmulti := $(foreach m,$(hostprogs),\
- $(if $($(m)-cxxobjs),,$(if $($(m)-objs),$(m))))
+ $(if $($(m)-cxxobjs)$($(m)-rust),,$(if $($(m)-objs),$(m))))
# Object (.o) files compiled from .c files
host-cobjs := $(sort $(foreach m,$(hostprogs),$($(m)-objs)))
@@ -58,11 +65,17 @@ host-cxxmulti := $(foreach m,$(hostprogs),$(if $($(m)-cxxobjs),$(m)))
# C++ Object (.o) files compiled from .cc files
host-cxxobjs := $(sort $(foreach m,$(host-cxxmulti),$($(m)-cxxobjs)))
+# Rust code
+# Executables compiled from a single Rust crate (which may consist of
+# one or more .rs files)
+host-rust := $(foreach m,$(hostprogs),$(if $($(m)-rust),$(m)))
+
host-csingle := $(addprefix $(obj)/,$(host-csingle))
host-cmulti := $(addprefix $(obj)/,$(host-cmulti))
host-cobjs := $(addprefix $(obj)/,$(host-cobjs))
host-cxxmulti := $(addprefix $(obj)/,$(host-cxxmulti))
host-cxxobjs := $(addprefix $(obj)/,$(host-cxxobjs))
+host-rust := $(addprefix $(obj)/,$(host-rust))
#####
# Handle options to gcc. Support building with separate output directory
@@ -71,6 +84,8 @@ _hostc_flags = $(KBUILD_HOSTCFLAGS) $(HOST_EXTRACFLAGS) \
$(HOSTCFLAGS_$(target-stem).o)
_hostcxx_flags = $(KBUILD_HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) \
$(HOSTCXXFLAGS_$(target-stem).o)
+_hostrust_flags = $(KBUILD_HOSTRUSTFLAGS) $(HOST_EXTRARUSTFLAGS) \
+ $(HOSTRUSTFLAGS_$(target-stem))
# $(objtree)/$(obj) for including generated headers from checkin source files
ifeq ($(KBUILD_EXTMOD),)
@@ -82,6 +97,7 @@ endif
hostc_flags = -Wp,-MMD,$(depfile) $(_hostc_flags)
hostcxx_flags = -Wp,-MMD,$(depfile) $(_hostcxx_flags)
+hostrust_flags = $(_hostrust_flags)
#####
# Compile programs on the host
@@ -128,5 +144,17 @@ quiet_cmd_host-cxxobjs = HOSTCXX $@
$(host-cxxobjs): $(obj)/%.o: $(src)/%.cc FORCE
$(call if_changed_dep,host-cxxobjs)
+# Create executable from a single Rust crate (which may consist of
+# one or more `.rs` files)
+# host-rust -> Executable
+quiet_cmd_host-rust = HOSTRUSTC $@
+ cmd_host-rust = \
+ $(HOSTRUSTC) $(hostrust_flags) --emit=dep-info,link \
+ --out-dir=$(obj)/ $<; \
+ mv $(obj)/$(target-stem).d $(depfile); \
+ sed -i '/^\#/d' $(depfile)
+$(host-rust): $(obj)/%: $(src)/%.rs FORCE
+ $(call if_changed_dep,host-rust)
+
targets += $(host-csingle) $(host-cmulti) $(host-cobjs) \
- $(host-cxxmulti) $(host-cxxobjs)
+ $(host-cxxmulti) $(host-cxxobjs) $(host-rust)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index d1425778664b..2fb41de030d5 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -8,6 +8,7 @@ ldflags-y += $(EXTRA_LDFLAGS)
# flags that take effect in current and sub directories
KBUILD_AFLAGS += $(subdir-asflags-y)
KBUILD_CFLAGS += $(subdir-ccflags-y)
+KBUILD_RUSTFLAGS += $(subdir-rustflags-y)
# Figure out what we need to build from the various variables
# ===========================================================================
@@ -128,6 +129,10 @@ _c_flags = $(filter-out $(CFLAGS_REMOVE_$(target-stem).o), \
$(filter-out $(ccflags-remove-y), \
$(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(ccflags-y)) \
$(CFLAGS_$(target-stem).o))
+_rust_flags = $(filter-out $(RUSTFLAGS_REMOVE_$(target-stem).o), \
+ $(filter-out $(rustflags-remove-y), \
+ $(KBUILD_RUSTFLAGS) $(rustflags-y)) \
+ $(RUSTFLAGS_$(target-stem).o))
_a_flags = $(filter-out $(AFLAGS_REMOVE_$(target-stem).o), \
$(filter-out $(asflags-remove-y), \
$(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(asflags-y)) \
@@ -202,6 +207,11 @@ modkern_cflags = \
$(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \
$(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags))
+modkern_rustflags = \
+ $(if $(part-of-module), \
+ $(KBUILD_RUSTFLAGS_MODULE) $(RUSTFLAGS_MODULE), \
+ $(KBUILD_RUSTFLAGS_KERNEL) $(RUSTFLAGS_KERNEL))
+
modkern_aflags = $(if $(part-of-module), \
$(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \
$(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL))
@@ -211,6 +221,8 @@ c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
$(_c_flags) $(modkern_cflags) \
$(basename_flags) $(modname_flags)
+rust_flags = $(_rust_flags) $(modkern_rustflags) @$(objtree)/include/generated/rustc_cfg
+
a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \
$(_a_flags) $(modkern_aflags)
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 35100e981f4a..9a1fa6aa30fe 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -39,11 +39,13 @@ quiet_cmd_ld_ko_o = LD [M] $@
quiet_cmd_btf_ko = BTF [M] $@
cmd_btf_ko = \
- if [ -f vmlinux ]; then \
+ if [ ! -f vmlinux ]; then \
+ printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
+ elif [ -n "$(CONFIG_RUST)" ] && $(srctree)/scripts/is_rust_module.sh $@; then \
+ printf "Skipping BTF generation for %s because it's a Rust module\n" $@ 1>&2; \
+ else \
LLVM_OBJCOPY="$(OBJCOPY)" $(PAHOLE) -J $(PAHOLE_FLAGS) --btf_base vmlinux $@; \
$(RESOLVE_BTFIDS) -b vmlinux $@; \
- else \
- printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
fi;
# Same as newer-prereqs, but allows to exclude specified extra dependencies
diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
index 855b937e7585..a0ec321469bd 100755
--- a/scripts/bpf_doc.py
+++ b/scripts/bpf_doc.py
@@ -635,6 +635,8 @@ class PrinterHelpers(Printer):
'struct bpf_timer',
'struct mptcp_sock',
'struct bpf_dynptr',
+ 'struct iphdr',
+ 'struct ipv6hdr',
]
known_types = {
'...',
@@ -686,6 +688,8 @@ class PrinterHelpers(Printer):
'struct bpf_timer',
'struct mptcp_sock',
'struct bpf_dynptr',
+ 'struct iphdr',
+ 'struct ipv6hdr',
}
mapped_types = {
'u8': '__u8',
diff --git a/scripts/cc-version.sh b/scripts/cc-version.sh
index f1952c522466..2401c86fcf53 100755
--- a/scripts/cc-version.sh
+++ b/scripts/cc-version.sh
@@ -1,13 +1,13 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
#
-# Print the compiler name and its version in a 5 or 6-digit form.
+# Print the C compiler name and its version in a 5 or 6-digit form.
# Also, perform the minimum version check.
set -e
-# Print the compiler name and some version components.
-get_compiler_info()
+# Print the C compiler name and some version components.
+get_c_compiler_info()
{
cat <<- EOF | "$@" -E -P -x c - 2>/dev/null
#if defined(__clang__)
@@ -32,7 +32,7 @@ get_canonical_version()
# $@ instead of $1 because multiple words might be given, e.g. CC="ccache gcc".
orig_args="$@"
-set -- $(get_compiler_info "$@")
+set -- $(get_c_compiler_info "$@")
name=$1
@@ -52,7 +52,7 @@ ICC)
min_version=$($min_tool_version icc)
;;
*)
- echo "$orig_args: unknown compiler" >&2
+ echo "$orig_args: unknown C compiler" >&2
exit 1
;;
esac
@@ -62,7 +62,7 @@ min_cversion=$(get_canonical_version $min_version)
if [ "$cversion" -lt "$min_cversion" ]; then
echo >&2 "***"
- echo >&2 "*** Compiler is too old."
+ echo >&2 "*** C compiler is too old."
echo >&2 "*** Your $name version: $version"
echo >&2 "*** Minimum $name version: $min_version"
echo >&2 "***"
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 79e759aac543..bf7c5abeefaa 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -3616,7 +3616,7 @@ sub process {
my $comment = "";
if ($realfile =~ /\.(h|s|S)$/) {
$comment = '/*';
- } elsif ($realfile =~ /\.(c|dts|dtsi)$/) {
+ } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) {
$comment = '//';
} elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) {
$comment = '#';
@@ -3664,7 +3664,7 @@ sub process {
}
# check we are in a valid source file if not then ignore this hunk
- next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts)$/);
+ next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/);
# check for using SPDX-License-Identifier on the wrong line number
if ($realline != $checklicenseline &&
@@ -6785,6 +6785,10 @@ sub process {
my $stat_real = get_stat_real($linenr, $lc);
my $ext_type = "Invalid";
my $use = "";
+ if ($bad_specifier =~ /pA/) {
+ ERROR("VSPRINTF_RUST",
+ "'\%pA' is only intended to be used from Rust code\n" . "$here\n$stat_real\n");
+ }
if ($bad_specifier =~ /p[Ff]/) {
$use = " - use %pS instead";
$use =~ s/pS/ps/ if ($bad_specifier =~ /pf/);
diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh
index 7075e26ab2c4..564c5632e1a2 100755
--- a/scripts/decode_stacktrace.sh
+++ b/scripts/decode_stacktrace.sh
@@ -8,6 +8,14 @@ usage() {
echo " $0 -r <release> | <vmlinux> [<base path>|auto] [<modules path>]"
}
+# Try to find a Rust demangler
+if type llvm-cxxfilt >/dev/null 2>&1 ; then
+ cppfilt=llvm-cxxfilt
+elif type c++filt >/dev/null 2>&1 ; then
+ cppfilt=c++filt
+ cppfilt_opts=-i
+fi
+
if [[ $1 == "-r" ]] ; then
vmlinux=""
basepath="auto"
@@ -180,6 +188,12 @@ parse_symbol() {
# In the case of inlines, move everything to same line
code=${code//$'\n'/' '}
+ # Demangle if the name looks like a Rust symbol and if
+ # we got a Rust demangler
+ if [[ $name =~ ^_R && $cppfilt != "" ]] ; then
+ name=$("$cppfilt" "$cppfilt_opts" "$name")
+ fi
+
# Replace old address with pretty line numbers
symbol="$segment$name ($code)"
}
diff --git a/scripts/gcc-plugins/latent_entropy_plugin.c b/scripts/gcc-plugins/latent_entropy_plugin.c
index 848918764174..39e86be60dd2 100644
--- a/scripts/gcc-plugins/latent_entropy_plugin.c
+++ b/scripts/gcc-plugins/latent_entropy_plugin.c
@@ -1,7 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2012-2016 by the PaX Team <pageexec@freemail.hu>
* Copyright 2016 by Emese Revfy <re.emese@gmail.com>
- * Licensed under the GPL v2
*
* Note: the choice of the license means that the compilation process is
* NOT 'eligible' as defined by gcc's library exception to the GPL v3,
diff --git a/scripts/gcc-plugins/stackleak_plugin.c b/scripts/gcc-plugins/stackleak_plugin.c
index ff91885f9470..c5c2ce113c92 100644
--- a/scripts/gcc-plugins/stackleak_plugin.c
+++ b/scripts/gcc-plugins/stackleak_plugin.c
@@ -1,7 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2011-2017 by the PaX Team <pageexec@freemail.hu>
* Modified by Alexander Popov <alex.popov@linux.com>
- * Licensed under the GPL v2
*
* Note: the choice of the license means that the compilation process is
* NOT 'eligible' as defined by gcc's library exception to the GPL v3,
diff --git a/scripts/gcc-plugins/structleak_plugin.c b/scripts/gcc-plugins/structleak_plugin.c
index 8bc04068ed39..d8c744233832 100644
--- a/scripts/gcc-plugins/structleak_plugin.c
+++ b/scripts/gcc-plugins/structleak_plugin.c
@@ -1,6 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2013-2017 by PaX Team <pageexec@freemail.hu>
- * Licensed under the GPL v2
*
* Note: the choice of the license means that the compilation process is
* NOT 'eligible' as defined by gcc's library exception to the GPL v3,
diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py
new file mode 100755
index 000000000000..37c049fb18f2
--- /dev/null
+++ b/scripts/generate_rust_analyzer.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""generate_rust_analyzer - Generates the `rust-project.json` file for `rust-analyzer`.
+"""
+
+import argparse
+import json
+import logging
+import pathlib
+import sys
+
+def generate_crates(srctree, objtree, sysroot_src):
+ # Generate the configuration list.
+ cfg = []
+ with open(objtree / "include" / "generated" / "rustc_cfg") as fd:
+ for line in fd:
+ line = line.replace("--cfg=", "")
+ line = line.replace("\n", "")
+ cfg.append(line)
+
+ # Now fill the crates list -- dependencies need to come first.
+ #
+ # Avoid O(n^2) iterations by keeping a map of indexes.
+ crates = []
+ crates_indexes = {}
+
+ def append_crate(display_name, root_module, deps, cfg=[], is_workspace_member=True, is_proc_macro=False):
+ crates_indexes[display_name] = len(crates)
+ crates.append({
+ "display_name": display_name,
+ "root_module": str(root_module),
+ "is_workspace_member": is_workspace_member,
+ "is_proc_macro": is_proc_macro,
+ "deps": [{"crate": crates_indexes[dep], "name": dep} for dep in deps],
+ "cfg": cfg,
+ "edition": "2021",
+ "env": {
+ "RUST_MODFILE": "This is only for rust-analyzer"
+ }
+ })
+
+ # First, the ones in `rust/` since they are a bit special.
+ append_crate(
+ "core",
+ sysroot_src / "core" / "src" / "lib.rs",
+ [],
+ is_workspace_member=False,
+ )
+
+ append_crate(
+ "compiler_builtins",
+ srctree / "rust" / "compiler_builtins.rs",
+ [],
+ )
+
+ append_crate(
+ "alloc",
+ srctree / "rust" / "alloc" / "lib.rs",
+ ["core", "compiler_builtins"],
+ )
+
+ append_crate(
+ "macros",
+ srctree / "rust" / "macros" / "lib.rs",
+ [],
+ is_proc_macro=True,
+ )
+ crates[-1]["proc_macro_dylib_path"] = "rust/libmacros.so"
+
+ append_crate(
+ "build_error",
+ srctree / "rust" / "build_error.rs",
+ ["core", "compiler_builtins"],
+ )
+
+ append_crate(
+ "kernel",
+ srctree / "rust" / "kernel" / "lib.rs",
+ ["core", "alloc", "macros", "build_error"],
+ cfg=cfg,
+ )
+ crates[-1]["env"]["OBJTREE"] = str(objtree.resolve(True))
+ crates[-1]["source"] = {
+ "include_dirs": [
+ str(srctree / "rust" / "kernel"),
+ str(objtree / "rust")
+ ],
+ "exclude_dirs": [],
+ }
+
+ # Then, the rest outside of `rust/`.
+ #
+ # We explicitly mention the top-level folders we want to cover.
+ for folder in ("samples", "drivers"):
+ for path in (srctree / folder).rglob("*.rs"):
+ logging.info("Checking %s", path)
+ name = path.name.replace(".rs", "")
+
+ # Skip those that are not crate roots.
+ if f"{name}.o" not in open(path.parent / "Makefile").read():
+ continue
+
+ logging.info("Adding %s", name)
+ append_crate(
+ name,
+ path,
+ ["core", "alloc", "kernel"],
+ cfg=cfg,
+ )
+
+ return crates
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--verbose', '-v', action='store_true')
+ parser.add_argument("srctree", type=pathlib.Path)
+ parser.add_argument("objtree", type=pathlib.Path)
+ parser.add_argument("sysroot_src", type=pathlib.Path)
+ args = parser.parse_args()
+
+ logging.basicConfig(
+ format="[%(asctime)s] [%(levelname)s] %(message)s",
+ level=logging.INFO if args.verbose else logging.WARNING
+ )
+
+ rust_project = {
+ "crates": generate_crates(args.srctree, args.objtree, args.sysroot_src),
+ "sysroot_src": str(args.sysroot_src),
+ }
+
+ json.dump(rust_project, sys.stdout, sort_keys=True, indent=4)
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs
new file mode 100644
index 000000000000..c146a3407183
--- /dev/null
+++ b/scripts/generate_rust_target.rs
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! The custom target specification file generator for `rustc`.
+//!
+//! To configure a target from scratch, a JSON-encoded file has to be passed
+//! to `rustc` (introduced in [RFC 131]). These options and the file itself are
+//! unstable. Eventually, `rustc` should provide a way to do this in a stable
+//! manner. For instance, via command-line arguments. Therefore, this file
+//! should avoid using keys which can be set via `-C` or `-Z` options.
+//!
+//! [RFC 131]: https://rust-lang.github.io/rfcs/0131-target-specification.html
+
+use std::{
+ collections::HashMap,
+ fmt::{Display, Formatter, Result},
+ io::BufRead,
+};
+
+enum Value {
+ Boolean(bool),
+ Number(i32),
+ String(String),
+ Object(Object),
+}
+
+type Object = Vec<(String, Value)>;
+
+/// Minimal "almost JSON" generator (e.g. no `null`s, no arrays, no escaping),
+/// enough for this purpose.
+impl Display for Value {
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
+ match self {
+ Value::Boolean(boolean) => write!(formatter, "{}", boolean),
+ Value::Number(number) => write!(formatter, "{}", number),
+ Value::String(string) => write!(formatter, "\"{}\"", string),
+ Value::Object(object) => {
+ formatter.write_str("{")?;
+ if let [ref rest @ .., ref last] = object[..] {
+ for (key, value) in rest {
+ write!(formatter, "\"{}\": {},", key, value)?;
+ }
+ write!(formatter, "\"{}\": {}", last.0, last.1)?;
+ }
+ formatter.write_str("}")
+ }
+ }
+ }
+}
+
+struct TargetSpec(Object);
+
+impl TargetSpec {
+ fn new() -> TargetSpec {
+ TargetSpec(Vec::new())
+ }
+}
+
+trait Push<T> {
+ fn push(&mut self, key: &str, value: T);
+}
+
+impl Push<bool> for TargetSpec {
+ fn push(&mut self, key: &str, value: bool) {
+ self.0.push((key.to_string(), Value::Boolean(value)));
+ }
+}
+
+impl Push<i32> for TargetSpec {
+ fn push(&mut self, key: &str, value: i32) {
+ self.0.push((key.to_string(), Value::Number(value)));
+ }
+}
+
+impl Push<String> for TargetSpec {
+ fn push(&mut self, key: &str, value: String) {
+ self.0.push((key.to_string(), Value::String(value)));
+ }
+}
+
+impl Push<&str> for TargetSpec {
+ fn push(&mut self, key: &str, value: &str) {
+ self.push(key, value.to_string());
+ }
+}
+
+impl Push<Object> for TargetSpec {
+ fn push(&mut self, key: &str, value: Object) {
+ self.0.push((key.to_string(), Value::Object(value)));
+ }
+}
+
+impl Display for TargetSpec {
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
+ // We add some newlines for clarity.
+ formatter.write_str("{\n")?;
+ if let [ref rest @ .., ref last] = self.0[..] {
+ for (key, value) in rest {
+ write!(formatter, " \"{}\": {},\n", key, value)?;
+ }
+ write!(formatter, " \"{}\": {}\n", last.0, last.1)?;
+ }
+ formatter.write_str("}")
+ }
+}
+
+struct KernelConfig(HashMap<String, String>);
+
+impl KernelConfig {
+ /// Parses `include/config/auto.conf` from `stdin`.
+ fn from_stdin() -> KernelConfig {
+ let mut result = HashMap::new();
+
+ let stdin = std::io::stdin();
+ let mut handle = stdin.lock();
+ let mut line = String::new();
+
+ loop {
+ line.clear();
+
+ if handle.read_line(&mut line).unwrap() == 0 {
+ break;
+ }
+
+ if line.starts_with('#') {
+ continue;
+ }
+
+ let (key, value) = line.split_once('=').expect("Missing `=` in line.");
+ result.insert(key.to_string(), value.trim_end_matches('\n').to_string());
+ }
+
+ KernelConfig(result)
+ }
+
+ /// Does the option exist in the configuration (any value)?
+ ///
+ /// The argument must be passed without the `CONFIG_` prefix.
+ /// This avoids repetition and it also avoids `fixdep` making us
+ /// depend on it.
+ fn has(&self, option: &str) -> bool {
+ let option = "CONFIG_".to_owned() + option;
+ self.0.contains_key(&option)
+ }
+}
+
+fn main() {
+ let cfg = KernelConfig::from_stdin();
+ let mut ts = TargetSpec::new();
+
+ // `llvm-target`s are taken from `scripts/Makefile.clang`.
+ if cfg.has("ARM") {
+ ts.push("arch", "arm");
+ ts.push(
+ "data-layout",
+ "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
+ );
+ ts.push("features", "+strict-align,+v6");
+ ts.push("llvm-target", "arm-linux-gnueabi");
+ ts.push("max-atomic-width", 64);
+ ts.push("target-mcount", "\\u0001__gnu_mcount_nc");
+ ts.push("target-pointer-width", "32");
+ } else if cfg.has("ARM64") {
+ ts.push("arch", "aarch64");
+ ts.push(
+ "data-layout",
+ "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
+ );
+ ts.push("disable-redzone", true);
+ ts.push("features", "+strict-align,-neon,-fp-armv8");
+ ts.push("llvm-target", "aarch64-linux-gnu");
+ ts.push("max-atomic-width", 128);
+ ts.push("target-pointer-width", "64");
+ } else if cfg.has("PPC") {
+ ts.push("arch", "powerpc64");
+ ts.push("code-model", "large");
+ ts.push("data-layout", "e-m:e-i64:64-n32:64");
+ ts.push("features", "-altivec,-vsx,-hard-float");
+ ts.push("llvm-target", "powerpc64le-linux-gnu");
+ ts.push("max-atomic-width", 64);
+ ts.push("target-mcount", "_mcount");
+ ts.push("target-pointer-width", "64");
+ } else if cfg.has("RISCV") {
+ if cfg.has("64BIT") {
+ ts.push("arch", "riscv64");
+ ts.push("data-layout", "e-m:e-p:64:64-i64:64-i128:128-n64-S128");
+ ts.push("llvm-target", "riscv64-linux-gnu");
+ ts.push("target-pointer-width", "64");
+ } else {
+ ts.push("arch", "riscv32");
+ ts.push("data-layout", "e-m:e-p:32:32-i64:64-n32-S128");
+ ts.push("llvm-target", "riscv32-linux-gnu");
+ ts.push("target-pointer-width", "32");
+ }
+ ts.push("code-model", "medium");
+ ts.push("disable-redzone", true);
+ let mut features = "+m,+a".to_string();
+ if cfg.has("RISCV_ISA_C") {
+ features += ",+c";
+ }
+ ts.push("features", features);
+ } else if cfg.has("X86_64") {
+ ts.push("arch", "x86_64");
+ ts.push(
+ "data-layout",
+ "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
+ );
+ ts.push("llvm-target", "x86_64-linux-gnu");
+ ts.push("target-pointer-width", "64");
+ } else {
+ panic!("Unsupported architecture");
+ }
+
+ ts.push("emit-debug-gdb-scripts", false);
+ ts.push("frame-pointer", "may-omit");
+ ts.push(
+ "stack-probes",
+ vec![("kind".to_string(), Value::String("none".to_string()))],
+ );
+
+ // Everything else is LE, whether `CPU_LITTLE_ENDIAN` is declared or not
+ // (e.g. x86). It is also `rustc`'s default.
+ if cfg.has("CPU_BIG_ENDIAN") {
+ ts.push("target-endian", "big");
+ }
+
+ println!("{}", ts);
+}
diff --git a/scripts/is_rust_module.sh b/scripts/is_rust_module.sh
new file mode 100755
index 000000000000..277a64d07f22
--- /dev/null
+++ b/scripts/is_rust_module.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+#
+# is_rust_module.sh module.ko
+#
+# Returns `0` if `module.ko` is a Rust module, `1` otherwise.
+
+set -e
+
+# Using the `16_` prefix ensures other symbols with the same substring
+# are not picked up (even if it would be unlikely). The last part is
+# used just in case LLVM decides to use the `.` suffix.
+${NM} "$*" | grep -qE '^[0-9a-fA-F]+ r _R[^[:space:]]+16___IS_RUST_MODULE[^[:space:]]*$'
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index f18e6dfc68c5..3b64dda7e4cc 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -27,7 +27,17 @@
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
-#define KSYM_NAME_LEN 128
+#define _stringify_1(x) #x
+#define _stringify(x) _stringify_1(x)
+
+#define KSYM_NAME_LEN 512
+
+/* A substantially bigger size than the current maximum. */
+#define KSYM_NAME_LEN_BUFFER 2048
+_Static_assert(
+ KSYM_NAME_LEN_BUFFER == KSYM_NAME_LEN * 4,
+ "Please keep KSYM_NAME_LEN_BUFFER in sync with KSYM_NAME_LEN"
+);
struct sym_entry {
unsigned long long addr;
@@ -198,15 +208,15 @@ static void check_symbol_range(const char *sym, unsigned long long addr,
static struct sym_entry *read_symbol(FILE *in)
{
- char name[500], type;
+ char name[KSYM_NAME_LEN_BUFFER+1], type;
unsigned long long addr;
unsigned int len;
struct sym_entry *sym;
int rc;
- rc = fscanf(in, "%llx %c %499s\n", &addr, &type, name);
+ rc = fscanf(in, "%llx %c %" _stringify(KSYM_NAME_LEN_BUFFER) "s\n", &addr, &type, name);
if (rc != 3) {
- if (rc != EOF && fgets(name, 500, in) == NULL)
+ if (rc != EOF && fgets(name, sizeof(name), in) == NULL)
fprintf(stderr, "Read error or end of file.\n");
return NULL;
}
@@ -471,12 +481,35 @@ static void write_src(void)
if ((i & 0xFF) == 0)
markers[i >> 8] = off;
- printf("\t.byte 0x%02x", table[i]->len);
+ /* There cannot be any symbol of length zero. */
+ if (table[i]->len == 0) {
+ fprintf(stderr, "kallsyms failure: "
+ "unexpected zero symbol length\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Only lengths that fit in up-to-two-byte ULEB128 are supported. */
+ if (table[i]->len > 0x3FFF) {
+ fprintf(stderr, "kallsyms failure: "
+ "unexpected huge symbol length\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Encode length with ULEB128. */
+ if (table[i]->len <= 0x7F) {
+ /* Most symbols use a single byte for the length. */
+ printf("\t.byte 0x%02x", table[i]->len);
+ off += table[i]->len + 1;
+ } else {
+ /* "Big" symbols use two bytes. */
+ printf("\t.byte 0x%02x, 0x%02x",
+ (table[i]->len & 0x7F) | 0x80,
+ (table[i]->len >> 7) & 0x7F);
+ off += table[i]->len + 2;
+ }
for (k = 0; k < table[i]->len; k++)
printf(", 0x%02x", table[i]->sym[k]);
printf("\n");
-
- off += table[i]->len + 1;
}
printf("\n");
diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c
index c4340c90e172..b7c9f1dd5e42 100644
--- a/scripts/kconfig/confdata.c
+++ b/scripts/kconfig/confdata.c
@@ -216,6 +216,13 @@ static const char *conf_get_autoheader_name(void)
return name ? name : "include/generated/autoconf.h";
}
+static const char *conf_get_rustccfg_name(void)
+{
+ char *name = getenv("KCONFIG_RUSTCCFG");
+
+ return name ? name : "include/generated/rustc_cfg";
+}
+
static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p)
{
char *p2;
@@ -605,6 +612,9 @@ static const struct comment_style comment_style_c = {
static void conf_write_heading(FILE *fp, const struct comment_style *cs)
{
+ if (!cs)
+ return;
+
fprintf(fp, "%s\n", cs->prefix);
fprintf(fp, "%s Automatically generated file; DO NOT EDIT.\n",
@@ -745,6 +755,65 @@ static void print_symbol_for_c(FILE *fp, struct symbol *sym)
free(escaped);
}
+static void print_symbol_for_rustccfg(FILE *fp, struct symbol *sym)
+{
+ const char *val;
+ const char *val_prefix = "";
+ char *val_prefixed = NULL;
+ size_t val_prefixed_len;
+ char *escaped = NULL;
+
+ if (sym->type == S_UNKNOWN)
+ return;
+
+ val = sym_get_string_value(sym);
+
+ switch (sym->type) {
+ case S_BOOLEAN:
+ case S_TRISTATE:
+ /*
+ * We do not care about disabled ones, i.e. no need for
+ * what otherwise are "comments" in other printers.
+ */
+ if (*val == 'n')
+ return;
+
+ /*
+ * To have similar functionality to the C macro `IS_ENABLED()`
+ * we provide an empty `--cfg CONFIG_X` here in both `y`
+ * and `m` cases.
+ *
+ * Then, the common `fprintf()` below will also give us
+ * a `--cfg CONFIG_X="y"` or `--cfg CONFIG_X="m"`, which can
+ * be used as the equivalent of `IS_BUILTIN()`/`IS_MODULE()`.
+ */
+ fprintf(fp, "--cfg=%s%s\n", CONFIG_, sym->name);
+ break;
+ case S_HEX:
+ if (val[0] != '0' || (val[1] != 'x' && val[1] != 'X'))
+ val_prefix = "0x";
+ break;
+ default:
+ break;
+ }
+
+ if (strlen(val_prefix) > 0) {
+ val_prefixed_len = strlen(val) + strlen(val_prefix) + 1;
+ val_prefixed = xmalloc(val_prefixed_len);
+ snprintf(val_prefixed, val_prefixed_len, "%s%s", val_prefix, val);
+ val = val_prefixed;
+ }
+
+ /* All values get escaped: the `--cfg` option only takes strings */
+ escaped = escape_string_value(val);
+ val = escaped;
+
+ fprintf(fp, "--cfg=%s%s=%s\n", CONFIG_, sym->name, val);
+
+ free(escaped);
+ free(val_prefixed);
+}
+
/*
* Write out a minimal config.
* All values that has default values are skipped as this is redundant.
@@ -1132,6 +1201,12 @@ int conf_write_autoconf(int overwrite)
if (ret)
return ret;
+ ret = __conf_write_autoconf(conf_get_rustccfg_name(),
+ print_symbol_for_rustccfg,
+ NULL);
+ if (ret)
+ return ret;
+
/*
* Create include/config/auto.conf. This must be the last step because
* Kbuild has a dependency on auto.conf and this marks the successful
diff --git a/scripts/kernel-doc b/scripts/kernel-doc
index 7516949bb049..aea04365bc69 100755
--- a/scripts/kernel-doc
+++ b/scripts/kernel-doc
@@ -427,6 +427,13 @@ sub print_lineno {
print ".. LINENO " . $lineno . "\n";
}
}
+
+sub emit_warning {
+ my $location = shift;
+ my $msg = shift;
+ print STDERR "$location: warning: $msg";
+ ++$warnings;
+}
##
# dumps section contents to arrays/hashes intended for that purpose.
#
@@ -451,8 +458,7 @@ sub dump_section {
if (defined($sections{$name}) && ($sections{$name} ne "")) {
# Only warn on user specified duplicate section names.
if ($name ne $section_default) {
- print STDERR "${file}:$.: warning: duplicate section name '$name'\n";
- ++$warnings;
+ emit_warning("${file}:$.", "duplicate section name '$name'\n");
}
$sections{$name} .= $contents;
} else {
@@ -1094,7 +1100,7 @@ sub dump_struct($$) {
if ($members) {
if ($identifier ne $declaration_name) {
- print STDERR "${file}:$.: warning: expecting prototype for $decl_type $identifier. Prototype was for $decl_type $declaration_name instead\n";
+ emit_warning("${file}:$.", "expecting prototype for $decl_type $identifier. Prototype was for $decl_type $declaration_name instead\n");
return;
}
@@ -1298,9 +1304,9 @@ sub dump_enum($$) {
if ($members) {
if ($identifier ne $declaration_name) {
if ($identifier eq "") {
- print STDERR "${file}:$.: warning: wrong kernel-doc identifier on line:\n";
+ emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n");
} else {
- print STDERR "${file}:$.: warning: expecting prototype for enum $identifier. Prototype was for enum $declaration_name instead\n";
+ emit_warning("${file}:$.", "expecting prototype for enum $identifier. Prototype was for enum $declaration_name instead\n");
}
return;
}
@@ -1316,7 +1322,7 @@ sub dump_enum($$) {
if (!$parameterdescs{$arg}) {
$parameterdescs{$arg} = $undescribed;
if (show_warnings("enum", $declaration_name)) {
- print STDERR "${file}:$.: warning: Enum value '$arg' not described in enum '$declaration_name'\n";
+ emit_warning("${file}:$.", "Enum value '$arg' not described in enum '$declaration_name'\n");
}
}
$_members{$arg} = 1;
@@ -1325,7 +1331,7 @@ sub dump_enum($$) {
while (my ($k, $v) = each %parameterdescs) {
if (!exists($_members{$k})) {
if (show_warnings("enum", $declaration_name)) {
- print STDERR "${file}:$.: warning: Excess enum value '$k' description in '$declaration_name'\n";
+ emit_warning("${file}:$.", "Excess enum value '$k' description in '$declaration_name'\n");
}
}
}
@@ -1367,7 +1373,7 @@ sub dump_typedef($$) {
$return_type =~ s/^\s+//;
if ($identifier ne $declaration_name) {
- print STDERR "${file}:$.: warning: expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n";
+ emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n");
return;
}
@@ -1398,7 +1404,7 @@ sub dump_typedef($$) {
$declaration_name = $1;
if ($identifier ne $declaration_name) {
- print STDERR "${file}:$.: warning: expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n";
+ emit_warning("${file}:$.", "expecting prototype for typedef $identifier. Prototype was for typedef $declaration_name instead\n");
return;
}
@@ -1554,9 +1560,7 @@ sub push_parameter($$$$$) {
$parameterdescs{$param} = $undescribed;
if (show_warnings($type, $declaration_name) && $param !~ /\./) {
- print STDERR
- "${file}:$.: warning: Function parameter or member '$param' not described in '$declaration_name'\n";
- ++$warnings;
+ emit_warning("${file}:$.", "Function parameter or member '$param' not described in '$declaration_name'\n");
}
}
@@ -1604,11 +1608,10 @@ sub check_sections($$$$$) {
}
if ($err) {
if ($decl_type eq "function") {
- print STDERR "${file}:$.: warning: " .
+ emit_warning("${file}:$.",
"Excess function parameter " .
"'$sects[$sx]' " .
- "description in '$decl_name'\n";
- ++$warnings;
+ "description in '$decl_name'\n");
}
}
}
@@ -1629,10 +1632,9 @@ sub check_return_section {
if (!defined($sections{$section_return}) ||
$sections{$section_return} eq "") {
- print STDERR "${file}:$.: warning: " .
+ emit_warning("${file}:$.",
"No description found for return value of " .
- "'$declaration_name'\n";
- ++$warnings;
+ "'$declaration_name'\n");
}
}
@@ -1714,12 +1716,12 @@ sub dump_function($$) {
create_parameterlist($args, ',', $file, $declaration_name);
} else {
- print STDERR "${file}:$.: warning: cannot understand function prototype: '$prototype'\n";
+ emit_warning("${file}:$.", "cannot understand function prototype: '$prototype'\n");
return;
}
if ($identifier ne $declaration_name) {
- print STDERR "${file}:$.: warning: expecting prototype for $identifier(). Prototype was for $declaration_name() instead\n";
+ emit_warning("${file}:$.", "expecting prototype for $identifier(). Prototype was for $declaration_name() instead\n");
return;
}
@@ -1801,8 +1803,8 @@ sub tracepoint_munge($) {
$tracepointargs = $1;
}
if (($tracepointname eq 0) || ($tracepointargs eq 0)) {
- print STDERR "${file}:$.: warning: Unrecognized tracepoint format: \n".
- "$prototype\n";
+ emit_warning("${file}:$.", "Unrecognized tracepoint format: \n".
+ "$prototype\n");
} else {
$prototype = "static inline void trace_$tracepointname($tracepointargs)";
$identifier = "trace_$identifier";
@@ -2027,22 +2029,16 @@ sub process_name($$) {
}
if (!$is_kernel_comment) {
- print STDERR "${file}:$.: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n";
- print STDERR $_;
- ++$warnings;
+ emit_warning("${file}:$.", "This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst\n$_");
$state = STATE_NORMAL;
}
if (($declaration_purpose eq "") && $verbose) {
- print STDERR "${file}:$.: warning: missing initial short description on line:\n";
- print STDERR $_;
- ++$warnings;
+ emit_warning("${file}:$.", "missing initial short description on line:\n$_");
}
if ($identifier eq "" && $decl_type ne "enum") {
- print STDERR "${file}:$.: warning: wrong kernel-doc identifier on line:\n";
- print STDERR $_;
- ++$warnings;
+ emit_warning("${file}:$.", "wrong kernel-doc identifier on line:\n$_");
$state = STATE_NORMAL;
}
@@ -2050,9 +2046,7 @@ sub process_name($$) {
print STDERR "${file}:$.: info: Scanning doc for $decl_type $identifier\n";
}
} else {
- print STDERR "${file}:$.: warning: Cannot understand $_ on line $.",
- " - I thought it was a doc line\n";
- ++$warnings;
+ emit_warning("${file}:$.", "Cannot understand $_ on line $. - I thought it was a doc line\n");
$state = STATE_NORMAL;
}
}
@@ -2071,8 +2065,7 @@ sub process_body($$) {
$section =~ s/\.\.\.$//;
if ($verbose) {
- print STDERR "${file}:$.: warning: Variable macro arguments should be documented without dots\n";
- ++$warnings;
+ emit_warning("${file}:$.", "Variable macro arguments should be documented without dots\n");
}
}
@@ -2101,8 +2094,7 @@ sub process_body($$) {
if (($contents ne "") && ($contents ne "\n")) {
if (!$in_doc_sect && $verbose) {
- print STDERR "${file}:$.: warning: contents before sections\n";
- ++$warnings;
+ emit_warning("${file}:$.", "contents before sections\n");
}
dump_section($file, $section, $contents);
$section = $section_default;
@@ -2128,8 +2120,7 @@ sub process_body($$) {
}
# look for doc_com + <text> + doc_end:
if ($_ =~ m'\s*\*\s*[a-zA-Z_0-9:\.]+\*/') {
- print STDERR "${file}:$.: warning: suspicious ending line: $_";
- ++$warnings;
+ emit_warning("${file}:$.", "suspicious ending line: $_");
}
$prototype = "";
@@ -2173,8 +2164,7 @@ sub process_body($$) {
}
} else {
# i dont know - bad line? ignore.
- print STDERR "${file}:$.: warning: bad line: $_";
- ++$warnings;
+ emit_warning("${file}:$.", "bad line: $_");
}
}
@@ -2268,9 +2258,7 @@ sub process_inline($$) {
}
} elsif ($inline_doc_state == STATE_INLINE_NAME) {
$inline_doc_state = STATE_INLINE_ERROR;
- print STDERR "${file}:$.: warning: ";
- print STDERR "Incorrect use of kernel-doc format: $_";
- ++$warnings;
+ emit_warning("${file}:$.", "Incorrect use of kernel-doc format: $_");
}
}
}
@@ -2319,11 +2307,11 @@ sub process_file($) {
if ($initial_section_counter == $section_counter && $
output_mode ne "none") {
if ($output_selection == OUTPUT_INCLUDE) {
- print STDERR "${file}:1: warning: '$_' not found\n"
+ emit_warning("${file}:1", "'$_' not found\n")
for keys %function_table;
}
else {
- print STDERR "${file}:1: warning: no structured comments found\n";
+ emit_warning("${file}:1", "no structured comments found\n");
}
}
close IN_FILE;
diff --git a/scripts/min-tool-version.sh b/scripts/min-tool-version.sh
index 250925aab101..f1e8358ec19a 100755
--- a/scripts/min-tool-version.sh
+++ b/scripts/min-tool-version.sh
@@ -30,6 +30,12 @@ llvm)
echo 11.0.0
fi
;;
+rustc)
+ echo 1.60.0
+ ;;
+bindgen)
+ echo 0.56.0
+ ;;
*)
echo "$1: unknown tool" >&2
exit 1
diff --git a/scripts/rust-is-available-bindgen-libclang.h b/scripts/rust-is-available-bindgen-libclang.h
new file mode 100644
index 000000000000..0ef6db10d674
--- /dev/null
+++ b/scripts/rust-is-available-bindgen-libclang.h
@@ -0,0 +1,2 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma message("clang version " __clang_version__)
diff --git a/scripts/rust-is-available.sh b/scripts/rust-is-available.sh
new file mode 100755
index 000000000000..6bd395167d0f
--- /dev/null
+++ b/scripts/rust-is-available.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+#
+# Tests whether a suitable Rust toolchain is available.
+#
+# Pass `-v` for human output and more checks (as warnings).
+
+set -e
+
+min_tool_version=$(dirname $0)/min-tool-version.sh
+
+# Convert the version string x.y.z to a canonical up-to-7-digits form.
+#
+# Note that this function uses one more digit (compared to other
+# instances in other version scripts) to give a bit more space to
+# `rustc` since it will reach 1.100.0 in late 2026.
+get_canonical_version()
+{
+ IFS=.
+ set -- $1
+ echo $((100000 * $1 + 100 * $2 + $3))
+}
+
+# Check that the Rust compiler exists.
+if ! command -v "$RUSTC" >/dev/null; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust compiler '$RUSTC' could not be found."
+ echo >&2 "***"
+ fi
+ exit 1
+fi
+
+# Check that the Rust bindings generator exists.
+if ! command -v "$BINDGEN" >/dev/null; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust bindings generator '$BINDGEN' could not be found."
+ echo >&2 "***"
+ fi
+ exit 1
+fi
+
+# Check that the Rust compiler version is suitable.
+#
+# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`.
+rust_compiler_version=$( \
+ LC_ALL=C "$RUSTC" --version 2>/dev/null \
+ | head -n 1 \
+ | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
+)
+rust_compiler_min_version=$($min_tool_version rustc)
+rust_compiler_cversion=$(get_canonical_version $rust_compiler_version)
+rust_compiler_min_cversion=$(get_canonical_version $rust_compiler_min_version)
+if [ "$rust_compiler_cversion" -lt "$rust_compiler_min_cversion" ]; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust compiler '$RUSTC' is too old."
+ echo >&2 "*** Your version: $rust_compiler_version"
+ echo >&2 "*** Minimum version: $rust_compiler_min_version"
+ echo >&2 "***"
+ fi
+ exit 1
+fi
+if [ "$1" = -v ] && [ "$rust_compiler_cversion" -gt "$rust_compiler_min_cversion" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust compiler '$RUSTC' is too new. This may or may not work."
+ echo >&2 "*** Your version: $rust_compiler_version"
+ echo >&2 "*** Expected version: $rust_compiler_min_version"
+ echo >&2 "***"
+fi
+
+# Check that the Rust bindings generator is suitable.
+#
+# Non-stable and distributions' versions may have a version suffix, e.g. `-dev`.
+rust_bindings_generator_version=$( \
+ LC_ALL=C "$BINDGEN" --version 2>/dev/null \
+ | head -n 1 \
+ | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
+)
+rust_bindings_generator_min_version=$($min_tool_version bindgen)
+rust_bindings_generator_cversion=$(get_canonical_version $rust_bindings_generator_version)
+rust_bindings_generator_min_cversion=$(get_canonical_version $rust_bindings_generator_min_version)
+if [ "$rust_bindings_generator_cversion" -lt "$rust_bindings_generator_min_cversion" ]; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust bindings generator '$BINDGEN' is too old."
+ echo >&2 "*** Your version: $rust_bindings_generator_version"
+ echo >&2 "*** Minimum version: $rust_bindings_generator_min_version"
+ echo >&2 "***"
+ fi
+ exit 1
+fi
+if [ "$1" = -v ] && [ "$rust_bindings_generator_cversion" -gt "$rust_bindings_generator_min_cversion" ]; then
+ echo >&2 "***"
+ echo >&2 "*** Rust bindings generator '$BINDGEN' is too new. This may or may not work."
+ echo >&2 "*** Your version: $rust_bindings_generator_version"
+ echo >&2 "*** Expected version: $rust_bindings_generator_min_version"
+ echo >&2 "***"
+fi
+
+# Check that the `libclang` used by the Rust bindings generator is suitable.
+bindgen_libclang_version=$( \
+ LC_ALL=C "$BINDGEN" $(dirname $0)/rust-is-available-bindgen-libclang.h 2>&1 >/dev/null \
+ | grep -F 'clang version ' \
+ | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \
+)
+bindgen_libclang_min_version=$($min_tool_version llvm)
+bindgen_libclang_cversion=$(get_canonical_version $bindgen_libclang_version)
+bindgen_libclang_min_cversion=$(get_canonical_version $bindgen_libclang_min_version)
+if [ "$bindgen_libclang_cversion" -lt "$bindgen_libclang_min_cversion" ]; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN') is too old."
+ echo >&2 "*** Your version: $bindgen_libclang_version"
+ echo >&2 "*** Minimum version: $bindgen_libclang_min_version"
+ echo >&2 "***"
+ fi
+ exit 1
+fi
+
+# If the C compiler is Clang, then we can also check whether its version
+# matches the `libclang` version used by the Rust bindings generator.
+#
+# In the future, we might be able to perform a full version check, see
+# https://github.com/rust-lang/rust-bindgen/issues/2138.
+if [ "$1" = -v ]; then
+ cc_name=$($(dirname $0)/cc-version.sh "$CC" | cut -f1 -d' ')
+ if [ "$cc_name" = Clang ]; then
+ clang_version=$( \
+ LC_ALL=C "$CC" --version 2>/dev/null \
+ | sed -nE '1s:.*version ([0-9]+\.[0-9]+\.[0-9]+).*:\1:p'
+ )
+ if [ "$clang_version" != "$bindgen_libclang_version" ]; then
+ echo >&2 "***"
+ echo >&2 "*** libclang (used by the Rust bindings generator '$BINDGEN') version does not match Clang's. This may be a problem."
+ echo >&2 "*** libclang version: $bindgen_libclang_version"
+ echo >&2 "*** Clang version: $clang_version"
+ echo >&2 "***"
+ fi
+ fi
+fi
+
+# Check that the source code for the `core` standard library exists.
+#
+# `$KRUSTFLAGS` is passed in case the user added `--sysroot`.
+rustc_sysroot=$("$RUSTC" $KRUSTFLAGS --print sysroot)
+rustc_src=${RUST_LIB_SRC:-"$rustc_sysroot/lib/rustlib/src/rust/library"}
+rustc_src_core="$rustc_src/core/src/lib.rs"
+if [ ! -e "$rustc_src_core" ]; then
+ if [ "$1" = -v ]; then
+ echo >&2 "***"
+ echo >&2 "*** Source code for the 'core' standard library could not be found"
+ echo >&2 "*** at '$rustc_src_core'."
+ echo >&2 "***"
+ fi
+ exit 1
+fi
diff --git a/scripts/rustdoc_test_builder.py b/scripts/rustdoc_test_builder.py
new file mode 100755
index 000000000000..d9b47a5c54fc
--- /dev/null
+++ b/scripts/rustdoc_test_builder.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""rustdoc_test_builder - Test builder for `rustdoc`-generated tests.
+"""
+
+import json
+import pathlib
+import re
+import sys
+
+RUST_DIR = pathlib.Path("rust")
+TESTS_DIR = RUST_DIR / "test" / "doctests" / "kernel"
+
+# `[^\s]*` removes the prefix (e.g. `_doctest_main_`) plus any
+# leading path (for `O=` builds).
+MAIN_RE = re.compile(
+ r"^"
+ r"fn main\(\) { "
+ r"#\[allow\(non_snake_case\)\] "
+ r"fn ([^\s]*rust_kernel_([a-zA-Z0-9_]+))\(\) {"
+ r"$"
+)
+
+def main():
+ found_main = False
+ test_header = ""
+ test_body = ""
+ for line in sys.stdin.readlines():
+ main_match = MAIN_RE.match(line)
+ if main_match:
+ if found_main:
+ raise Exception("More than one `main` line found.")
+ found_main = True
+ function_name = main_match.group(1)
+ test_name = f"rust_kernel_doctest_{main_match.group(2)}"
+ continue
+
+ if found_main:
+ test_body += line
+ else:
+ test_header += line
+
+ if not found_main:
+ raise Exception("No `main` line found.")
+
+ call_line = f"}} {function_name}() }}"
+ if not test_body.endswith(call_line):
+ raise Exception("Unexpected end of test body.")
+ test_body = test_body[:-len(call_line)]
+
+ with open(TESTS_DIR / f"{test_name}.json", "w") as fd:
+ json.dump({
+ "name": test_name,
+ "header": test_header,
+ "body": test_body,
+ }, fd, sort_keys=True, indent=4)
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/rustdoc_test_gen.py b/scripts/rustdoc_test_gen.py
new file mode 100755
index 000000000000..ad9a94293ab5
--- /dev/null
+++ b/scripts/rustdoc_test_gen.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""rustdoc_test_gen - Generates KUnit tests from saved `rustdoc`-generated tests.
+"""
+
+import json
+import os
+import pathlib
+
+RUST_DIR = pathlib.Path("rust")
+TESTS_DIR = RUST_DIR / "test" / "doctests" / "kernel"
+
+RUST_FILE = RUST_DIR / "doctests_kernel_generated.rs"
+C_FILE = RUST_DIR / "doctests_kernel_generated_kunit.c"
+
+RUST_TEMPLATE_TEST = """
+/// Generated `{test_name}` KUnit test case from a Rust documentation test.
+#[no_mangle]
+pub fn {test_name}(__kunit_test: *mut kernel::bindings::kunit) {{
+ /// Provides mutual exclusion (see `# Implementation` notes).
+ static __KUNIT_TEST_MUTEX: kernel::sync::smutex::Mutex<()> =
+ kernel::sync::smutex::Mutex::new(());
+
+ /// Saved argument (see `# Implementation` notes).
+ static __KUNIT_TEST: core::sync::atomic::AtomicPtr<kernel::bindings::kunit> =
+ core::sync::atomic::AtomicPtr::new(core::ptr::null_mut());
+
+ let __kunit_test_mutex_guard = __KUNIT_TEST_MUTEX.lock();
+ __KUNIT_TEST.store(__kunit_test, core::sync::atomic::Ordering::SeqCst);
+
+ /// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
+ macro_rules! assert {{
+ ($cond:expr $(,)?) => {{{{
+ kernel::kunit_assert!(
+ __KUNIT_TEST.load(core::sync::atomic::Ordering::SeqCst),
+ $cond
+ );
+ }}}}
+ }}
+
+ /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
+ macro_rules! assert_eq {{
+ ($left:expr, $right:expr $(,)?) => {{{{
+ kernel::kunit_assert_eq!(
+ __KUNIT_TEST.load(core::sync::atomic::Ordering::SeqCst),
+ $left,
+ $right
+ );
+ }}}}
+ }}
+
+ // Many tests need the prelude, so provide it by default.
+ use kernel::prelude::*;
+
+ {test_body}
+}}
+"""
+RUST_TEMPLATE = """// SPDX-License-Identifier: GPL-2.0
+
+//! `kernel` crate documentation tests.
+
+// # Implementation
+//
+// KUnit gives us a context in the form of the `kunit_test` parameter that one
+// needs to pass back to other KUnit functions and macros.
+//
+// However, we want to keep this as an implementation detail because:
+//
+// - Test code should not care about the implementation.
+//
+// - Documentation looks worse if it needs to carry extra details unrelated
+// to the piece being described.
+//
+// - Test code should be able to define functions and call them, without
+// having to carry the context (since functions cannot capture dynamic
+// environment).
+//
+// - Later on, we may want to be able to test non-kernel code (e.g. `core`,
+// `alloc` or external crates) which likely use the standard library
+// `assert*!` macros.
+//
+// For this reason, `static`s are used in the generated code to save the
+// argument which then gets read by the asserting macros. These macros then
+// call back into KUnit, instead of panicking.
+//
+// To avoid depending on whether KUnit allows to run tests concurrently and/or
+// reentrantly, we ensure mutual exclusion on our end. To ensure a single test
+// being killed does not trigger failure of every other test (timing out),
+// we provide different `static`s per test (which also allow for concurrent
+// execution, though KUnit runs them sequentially).
+//
+// Furthermore, since test code may create threads and assert from them, we use
+// an `AtomicPtr` to hold the context (though each test only writes once before
+// threads may be created).
+
+{rust_header}
+
+const __LOG_PREFIX: &[u8] = b"rust_kernel_doctests\\0";
+
+{rust_tests}
+"""
+
+C_TEMPLATE_TEST_DECLARATION = "void {test_name}(struct kunit *);\n"
+C_TEMPLATE_TEST_CASE = " KUNIT_CASE({test_name}),\n"
+C_TEMPLATE = """// SPDX-License-Identifier: GPL-2.0
+/*
+ * `kernel` crate documentation tests.
+ */
+
+#include <kunit/test.h>
+
+{c_test_declarations}
+
+static struct kunit_case test_cases[] = {{
+ {c_test_cases}
+ {{ }}
+}};
+
+static struct kunit_suite test_suite = {{
+ .name = "rust_kernel_doctests",
+ .test_cases = test_cases,
+}};
+
+kunit_test_suite(test_suite);
+
+MODULE_LICENSE("GPL");
+"""
+
+def main():
+ rust_header = set()
+ rust_tests = ""
+ c_test_declarations = ""
+ c_test_cases = ""
+ for filename in sorted(os.listdir(TESTS_DIR)):
+ with open(TESTS_DIR / filename, "r") as fd:
+ test = json.load(fd)
+ for line in test["header"].strip().split("\n"):
+ rust_header.add(line)
+ rust_tests += RUST_TEMPLATE_TEST.format(
+ test_name = test["name"],
+ test_body = test["body"]
+ )
+ c_test_declarations += C_TEMPLATE_TEST_DECLARATION.format(
+ test_name = test["name"]
+ )
+ c_test_cases += C_TEMPLATE_TEST_CASE.format(
+ test_name = test["name"]
+ )
+ rust_header = sorted(rust_header)
+
+ with open(RUST_FILE, "w") as fd:
+ fd.write(RUST_TEMPLATE.format(
+ rust_header = "\n".join(rust_header).strip(),
+ rust_tests = rust_tests.strip(),
+ ))
+
+ with open(C_FILE, "w") as fd:
+ fd.write(C_TEMPLATE.format(
+ c_test_declarations=c_test_declarations.strip(),
+ c_test_cases=c_test_cases.strip(),
+ ))
+
+if __name__ == "__main__":
+ main()