Subject: [RFC PATCH v3 1/2] kbuild: Build Rust crates as libraries

Enables compiling Rust crates as dependencies of kernel modules. These
are also meant to be used as libraries for other parts of the kernel.

For these crates `bindings` is also exposed to them and they get their
symbols exported for them to be used by other code.

When a composite object depends on an `.rlib` file, which by the way is
a current ar archive, Kbuild will compile it from its base Rust source
and archive it.

This makes possible to have Rust bindings for a subsystem that is
compiled either built-in or as a module. They can also be made into
modules by themselves too.

Signed-off-by: Martin Rodriguez Reboredo <[email protected]>
---
v2 -> v3:
- Fixed erroneous dependency on exported symbols from KUnit tests.
v1 -> v2:
- Fixed builtins compilation.
- Added support for building crates as modules.

.gitignore | 2 ++
Makefile | 4 ++--
scripts/Makefile.build | 42 ++++++++++++++++++++++++++++++++++++---
scripts/Makefile.lib | 20 +++++++++++++++----
scripts/Makefile.modfinal | 9 +++++++--
5 files changed, 66 insertions(+), 11 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0bbae167bf93..8353b01e2915 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@
*.o
*.o.*
*.patch
+*.rlib
*.rmeta
*.rpm
*.rsi
@@ -53,6 +54,7 @@
*.zst
Module.symvers
modules.order
+exports_*_generated.c

#
# Top-level generic files
diff --git a/Makefile b/Makefile
index 8a40530868ff..4113a5b93ddc 100644
--- a/Makefile
+++ b/Makefile
@@ -283,7 +283,7 @@ no-compiler-targets := $(no-dot-config-targets) install dtbs_install \
headers_install modules_install modules_sign kernelrelease image_name
no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \
image_name
-single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %.symtypes %/
+single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rlib %.rsi %.s %.symtypes %/

config-build :=
mixed-build :=
@@ -1919,7 +1919,7 @@ $(clean-dirs):
clean: $(clean-dirs)
$(call cmd,rmfiles)
@find $(or $(KBUILD_EXTMOD), .) $(RCS_FIND_IGNORE) \
- \( -name '*.[aios]' -o -name '*.rsi' -o -name '*.ko' -o -name '.*.cmd' \
+ \( -name '*.[aios]' -o -name '*.rlib' -o -name '*.rsi' -o -name '*.ko' -o -name '.*.cmd' \
-o -name '*.ko.*' \
-o -name '*.dtb' -o -name '*.dtbo' \
-o -name '*.dtb.S' -o -name '*.dtbo.S' \
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index da37bfa97211..d31b4272a79f 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -20,6 +20,7 @@ always-m :=
targets :=
subdir-y :=
subdir-m :=
+rust-libs :=
EXTRA_AFLAGS :=
EXTRA_CFLAGS :=
EXTRA_CPPFLAGS :=
@@ -274,7 +275,7 @@ rust_common_cmd = \
-Zcrate-attr='feature($(rust_allowed_features))' \
--extern alloc --extern kernel \
--crate-type rlib -L $(objtree)/rust/ \
- --crate-name $(basename $(notdir $@)) \
+ --crate-name $(basename $(notdir $<)) \
--out-dir $(dir $@) --emit=dep-info=$(depfile)

# `--emit=obj`, `--emit=asm` and `--emit=llvm-ir` imply a single codegen unit
@@ -285,11 +286,41 @@ rust_common_cmd = \
# i.e. the outputs we would get for the different single targets (e.g. `.ll`)
# would not match each other.

+rlib_obj = $(dir $@)$*.$*.o
+rlib_meta = $(dir $@)$(patsubst %.rlib,%.rmeta,$(notdir $@))
+
quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $<

+quiet_cmd_ln_rlib_o = SYMLINK $@
+ cmd_ln_rlib_o = ln -s -f $*.$*.o $@
+
$(obj)/%.o: $(src)/%.rs FORCE
- $(call if_changed_dep,rustc_o_rs)
+ $(if $(findstring $@,$(crate-obj-m)), \
+ $(call if_changed,ln_rlib_o), $(call if_changed_dep,rustc_o_rs))
+
+quiet_cmd_rustc_rlib_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
+ cmd_rustc_rlib_rs = $(rust_common_cmd) --extern bindings \
+ --emit=obj=$(rlib_obj) --emit=metadata=$(rlib_meta) $<; \
+ rm -f $@; $(AR) cDPrST $@ $(rlib_obj) $(rlib_meta)
+
+quiet_cmd_rlib_exports = EXPORTS $@
+ cmd_rlib_exports = \
+ echo "\#include <linux/module.h>" > $@; \
+ $(NM) -p --defined-only $(rlib_obj) \
+ | perl -ae '/ (T|R|D) (?!(init|cleanup)_module)/ and' \
+ -e 'print "extern int @F[2]; EXPORT_SYMBOL_GPL(@F[2]);"' >> $@
+
+$(crate-obj-m): $(obj)/%.o: $(obj)/exports_%_generated.o $(obj)/lib%.rlib $(src)/%.rs FORCE
+
+$(obj)/lib%.rlib: $(src)/%.rs FORCE
+ $(call if_changed_dep,rustc_rlib_rs)
+
+$(obj)/exports_%_generated.c: $(obj)/lib%.rlib FORCE
+ $(call if_changed,rlib_exports)
+
+$(obj)/exports_%_generated.o: $(obj)/exports_%_generated.c FORCE
+ $(call if_changed,cc_o_c)

quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
cmd_rustc_rsi_rs = \
@@ -394,9 +425,14 @@ $(subdir-modorder): $(obj)/%/modules.order: $(obj)/% ;
# To make this rule robust against "Argument list too long" error,
# remove $(obj)/ prefix, and restore it by a shell command.

+# If we have a .rlib for the archive change it for its object member
+crate-obj = $(foreach m, $1, $(if $(findstring .rlib, $m), \
+ $(basename $(m:lib%=%)).$(m:lib%.rlib=%.o),$m))
+
quiet_cmd_ar_builtin = AR $@
cmd_ar_builtin = rm -f $@; \
- $(if $(real-prereqs), printf "$(obj)/%s " $(patsubst $(obj)/%,%,$(real-prereqs)) | xargs) \
+ $(if $(real-prereqs), printf "$(obj)/%s " \
+ $(call crate-obj, $(patsubst $(obj)/%,%,$(real-prereqs))) | xargs) \
$(AR) cDPrST $@

$(obj)/built-in.a: $(real-obj-y) FORCE
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 68d0134bdbf9..3fe419250dbd 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -45,12 +45,17 @@ else
obj-y := $(filter-out %/, $(obj-y))
endif

+crate-obj-y := $(foreach m, $(filter lib%.rlib, $(obj-y)), $(m:lib%.rlib=%.o))
+crate-obj-m := $(foreach m, $(filter lib%.rlib, $(obj-m)), $(m:lib%.rlib=%.o))
+
# Expand $(foo-objs) $(foo-y) etc. by replacing their individuals
suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s))))
# List composite targets that are constructed by combining other targets
multi-search = $(sort $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $m)))
# List primitive targets that are compiled from source files
real-search = $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $(call suffix-search, $m, $2, $3), $m))
+# List exported symbols from .rlib targets
+rlib-exports = $(foreach m, $1, $(if $(findstring .rlib, $m),$(m:lib%.rlib=exports_%_generated.o)) $m)

# If $(foo-objs), $(foo-y), $(foo-m), or $(foo-) exists, foo.o is a composite object
multi-obj-y := $(call multi-search, $(obj-y), .o, -objs -y)
@@ -59,8 +64,8 @@ multi-obj-ym := $(multi-obj-y) $(multi-obj-m)

# Replace multi-part objects by their individual parts,
# including built-in.a from subdirectories
-real-obj-y := $(call real-search, $(obj-y), .o, -objs -y)
-real-obj-m := $(call real-search, $(obj-m), .o, -objs -y -m)
+real-obj-y := $(call rlib-exports, $(call real-search, $(obj-y), .o, -objs -y))
+real-obj-m := $(call rlib-exports, $(call real-search, $(obj-m), .o, -objs -y -m))

always-y += $(always-m)

@@ -96,6 +101,8 @@ always-y := $(addprefix $(obj)/,$(always-y))
targets := $(addprefix $(obj)/,$(targets))
obj-m := $(addprefix $(obj)/,$(obj-m))
lib-y := $(addprefix $(obj)/,$(lib-y))
+crate-obj-y := $(addprefix $(obj)/,$(crate-obj-y))
+crate-obj-m := $(addprefix $(obj)/,$(crate-obj-m))
real-obj-y := $(addprefix $(obj)/,$(real-obj-y))
real-obj-m := $(addprefix $(obj)/,$(real-obj-m))
multi-obj-m := $(addprefix $(obj)/, $(multi-obj-m))
@@ -210,7 +217,11 @@ _cpp_flags += -I $(srctree)/$(src) -I $(objtree)/$(obj)
endif
endif

-part-of-module = $(if $(filter $(basename $@).o, $(real-obj-m)),y)
+_rust_libs = $(foreach l, $(rust-libs), -L $(obj)/$(dir $l) --extern $(notdir $(basename $l)))
+
+part-of-module = \
+ $(if $(or $(filter $(basename $@).o, $(real-obj-m)), \
+ $(filter $(basename $@).rlib, $(real-obj-m))),y)
quiet_modtag = $(if $(part-of-module),[M], )

modkern_cflags = \
@@ -232,7 +243,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
+rust_flags = $(_rust_flags) $(modkern_rustflags) $(_rust_libs) \
+ @$(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 b3a6aa8fbe8c..aab17203f5b1 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -19,7 +19,7 @@ __modfinal: $(modules:%.o=%.ko)
@:

# modname and part-of-module are set to make c_flags define proper module flags
-modname = $(notdir $(@:.mod.o=))
+modname = $(notdir $*)
part-of-module = y

quiet_cmd_cc_o_c = CC [M] $@
@@ -30,11 +30,16 @@ quiet_cmd_cc_o_c = CC [M] $@

ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)

+# Symlinks of foo.o that point to foo.foo.o are members of a .rlib archives,
+# so their exports should be added to the module
+ko_objs = $(if $(filter %$(modname).$(modname).o,$(realpath $*.o)),\
+ $(dir $*)exports_$(modname)_generated.o) $(filter %.o, $^)
+
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o += \
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
- -T scripts/module.lds -o $@ $(filter %.o, $^); \
+ -T scripts/module.lds -o $@ $(ko_objs); \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

quiet_cmd_btf_ko = BTF [M] $@
--
2.42.1