Skip to content

Instantly share code, notes, and snippets.

@ma5ter
Last active February 16, 2026 13:56
Show Gist options
  • Select an option

  • Save ma5ter/50a63efeeaabe81793104b2b8df08666 to your computer and use it in GitHub Desktop.

Select an option

Save ma5ter/50a63efeeaabe81793104b2b8df08666 to your computer and use it in GitHub Desktop.
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

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

1) Create the Gentoo cross toolchain

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 crossdev
crossdev --show-fail-log -t mipsel-unknown-linux-gnu --ex-gdb

Note: avoid adding --stable because it breaks right now due to glibc testing state keywording.

2) Get the exact target kernel build inputs

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 -r

3) Prepare a matching kernel tree

The 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.

3.0 Install a matching kernel tree into the cross root

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
EOF

Update the cross-root @system set (pulls in baseline system packages for the target ROOT).

emerge-mipsel-unknown-linux-gnu --update --newuse --deep -av @system

Install 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-sources

3.1 Import the device config and fix CPU/platform knobs

Copy 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- olddefconfig

The 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- menuconfig

3.2 Freeze the local and extra version

The 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/' .config

or

sed -i 's/^EXTRAVERSION = .*/EXTRAVERSION =/' Makefile

3.3 Apply the SUBLEVEL “mimic” hack (if needed)

Note: 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/' Makefile

3.4 Generate the headers needed for external modules

The kernel documentation recommends modules_prepare for preparing a tree to build external modules.

make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- modules_prepare

3.5 Verify the release string before building modules

If make kernelrelease is wrong, your module’s vermagic will be wrong.

make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- -s kernelrelease

3.6 Add builtin modules information files

While 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.modinfo

Otherwise, build the kernel.

make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- make -j 16

3.7 Build your external module against this prepared tree

This 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 vermagic

3.8 Extract built-in modules if needed

When adding new CONFIG_*=m and building missed modules with

make ARCH=mips CROSS_COMPILE=mipsel-unknown-linux-gnu- modules

you 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_install

Important caveat about the SUBLEVEL hack

Forcing 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 insmod the rfcomm.ko build from .250 sublevel because .186 misses timer_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'.

Example for build and load a test “hello” module

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 vermagic

Copy to device and test

scp hello.ko root@DEVICE:/tmp/

On device:

insmod /tmp/hello.ko && rmmod hello; dmesg | tail -n 50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment