Comprehensive step-by-step instructions for building a module for a foreign MIPS kernel using Gentoo's crossdev environment in order to have missing modules (like rfcomm) for Creality 2025 K1C
Gentoo’s crossdev builds and installs a cross-compilation toolchain (binutils, GCC, kernel-headers, libc, etc.) for a given target tuple.
On your Gentoo host
emerge -av crossdevcrossdev --show-fail-log -t mipsel-unknown-linux-gnu --ex-gdbNote: avoid adding
--stablebecause it breaks right now due to glibc testing state keywording.
To build an external (out-of-tree) module, you need a prepared kernel build directory that contains the configuration and generated headers for the kernel you’re targeting.
On the target device: capture exact release and config
zcat /proc/config.gz > running-kernel.config
uname -rThe goal of this part is to create a kernel build directory whose generated headers and KERNELRELEASE string produce the same vermagic as the running
device kernel when you build your external .ko.
kbuild derives the release string (what make kernelrelease prints, what ends up in include/config/kernel.release, and what ultimately drives vermagic)
from
VERSION.PATCHLEVEL.SUBLEVEL plus extra local version bits generated by scripts/setlocalversion.
crossdev installs a target sysroot under /usr/<CHOST>/ and provide “decorated” wrapper commands like emerge-<CHOST> to install packages into that cross
root.
To keep Portage from pulling newer kernel headers/sources than you want, mask the higher versions using standard package.mask atoms (one per line).
Create a mask file inside the cross root's Portage config. (This keeps the mask scoped to the cross environment, not your host system.)
tee /usr/mipsel-unknown-linux-gnu/etc/portage/package.mask/02_not_welcomed >/dev/null <<'EOF'
>=sys-kernel/linux-headers-5.11
>=sys-kernel/gentoo-sources-5.11
EOFUpdate the cross-root @system set (pulls in baseline system packages for the target ROOT).
emerge-mipsel-unknown-linux-gnu --update --newuse --deep -av @systemInstall kernel sources into the cross root.
This will place sources under /usr/mipsel-unknown-linux-gnu/usr/src/
emerge-mipsel-unknown-linux-gnu -av sys-kernel/gentoo-sourcesCopy extracted config, rename to .config and invoke oldconfig form cross sysroot
cd /usr/mipsel-unknown-linux-gnu/usr/src/
cp /path/to/running-kernel.config ./.config
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- olddefconfigThe kernel vermagic should exactly match 5.10.186 SMP preempt mod_unload MIPS32_R5 32BIT .
The CPU variant part can be simply fixed by enabling CONFIG_MIPS_MALTA=y
make -C "$KSRC" O="$KOUT" ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- menuconfigThe release string can also change due to scripts/setlocalversion (git describe, dirty tree markers, etc.), which is why people often see extra suffixes in
uname -r. I.e. in case of portage installed kernel tree EXTRAVERSION is -gentoo
To keep KERNELRELEASE stable, turn off automatic local versioning and set an explicit local version if needed.
sed -i 's/^CONFIG_LOCALVERSION=.*/# CONFIG_LOCALVERSION_AUTO is not set/' .configor
sed -i 's/^EXTRAVERSION = .*/EXTRAVERSION =/' MakefileNote: prefer getting the exact version from https://www.kernel.org/pub/linux/kernel/ See caveats below!
If your sources are for example 5.10.250-ish bacause of deprecation of earlier versions but the device is running 5.10.186, you can force the release string
to say 5.10.186 by changing the top-level kernel
Makefile value.
This works because the kernel release string is computed from VERSION, PATCHLEVEL, SUBLEVEL, and EXTRAVERSION, plus the local version suffix from
scripts/setlocalversion.
sed -i 's/^SUBLEVEL = .*/SUBLEVEL = 186/' MakefileThe kernel documentation recommends modules_prepare for preparing a tree to build external
modules.
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- modules_prepareIf make kernelrelease is wrong, your module’s vermagic will be wrong.
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- -s kernelreleaseWhile installing modules from the kernel tree, if you change config to build standard modules, two files are needed:
| File |
|---|
| modules.builtin |
| modules.builtin.modinfo |
Normally they are produced by full kernel build, but you can skip it in case of building simle custom modules without dependencies and just touch empty ones.
touch modules.builtin modules.builtin.modinfoOtherwise, build the kernel.
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- make -j 16This is the canonical external-module invocation. In your module directory (where obj-m := ... lives)
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu-Confirm vermagic matches what the device wants (including MIPS32_R5, SMP/PREEMPT, etc.)
modinfo *.ko | grep vermagicWhen adding new CONFIG_*=m and building missed modules with
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- modulesyou need to extract new modules before porting them to device.
This can be simply done by modules_install with modified INSTALL_MOD_PATH, i.e.
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- INSTALL_MOD_PATH=/tmp/extracted modules_installForcing SUBLEVEL makes the string match, but it does not guarantee ABI/API compatibility with a kernel actually built from a source baseline (the vermagic gate is not a full compatibility proof).
Use it only when you can’t get the exact vendor tree but you’ve already validated (as you did) that a real module loads and runs on the device, and then keep changes minimal and test carefully.
E.g. you will be unable to
insmodtherfcomm.kobuild from .250 sublevel because .186 missestimer_delete_sync()and you will get 'rfcomm: Unknown symbol timer_delete_sync (err -2)' upon insert. But switching to https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.10.186.tar.xz will give 'Bluetooth: RFCOMM ver 1.11'.
Create a minimal module workspace
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("minimal test module");
static int __init hello_init(void)
{
printk(KERN_INFO "hello_test: init\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "hello_test: exit\n");
}
module_init(hello_init);
module_exit(hello_exit);obj-m := hello.o
KDIR ?= ../linux
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
Build
make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu-Inspect vermagic; it must match the target kernel (including MIPS32_R5 vs R2, SMP/PREEMPT, etc.)
modinfo hello.ko | grep vermagicCopy to device and test
scp hello.ko root@DEVICE:/tmp/On device:
insmod /tmp/hello.ko && rmmod hello; dmesg | tail -n 50