Integrate out-of-tree DKMS modules into the kernel at build time - #54
Open
Bjordis Collaku (bjordiscollaku) wants to merge 13 commits into
Open
Integrate out-of-tree DKMS modules into the kernel at build time#54Bjordis Collaku (bjordiscollaku) wants to merge 13 commits into
Bjordis Collaku (bjordiscollaku) wants to merge 13 commits into
Conversation
kgsl-dkms provides the KGSL GPU driver source and DKMS configuration. Listing it as a Build-Depends makes dpkg-buildpackage install it into the build environment before any debian/rules target runs, giving the DKMS build step in override_dh_auto_install access to the source tree at /usr/src/kgsl-<version>/. dkms is the build-time tool that compiles the module. Neither package appears in the binary package Depends/Recommends/Suggests — they are consumed during the build and never reach the target device. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
…nux-image Build the module from its installed -dkms source with `dkms build`, against the kernel headers staged earlier in the same dpkg-buildpackage run, and install the resulting .ko into linux-image under /lib/modules/<KVER>/extra/. Use a private --dkmstree (mktemp) with a hand-created source symlink so the build runs rootless under fakeroot without writing to the root-owned /var/lib/dkms, and pass an absolute --kernelsourcedir via $(CURDIR) because dkms invokes make from inside the module source directory. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
Add debian/dkms-modules, a plain-text list of modules to build and bundle, and read it in debian/rules so the build loops over every listed module instead of a single hardcoded block. Adding a module is then a one-line manifest entry plus its Build-Depends, with no debian/rules change. Comments (# to end of line), blank lines, and CR line endings are stripped when the manifest is read, so an annotated or CRLF-edited manifest cannot leak stray tokens into the module list. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
dkms build can complete without producing a module, for example when a BUILD_EXCLUSIVE_* directive in the module's dkms.conf does not match the target kernel, and the bundling loop would previously just bundle nothing. A manifest entry is a presence contract: capture the built .ko list and hard-fail when it is empty, instead of silently shipping linux-image without a declared module. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
dkms --arch and dkms.conf BUILD_EXCLUSIVE_ARCH speak uname -m vocabulary (aarch64), while the build passed a hardcoded kbuild-style token (arm64). The mismatch is latent today only because kgsl declares no BUILD_EXCLUSIVE_ARCH gate; a module that does would be wrongly gated off by the arm64 token. Introduce DKMS_ARCH (aarch64) alongside the kbuild ARCH variable and pass that instead. Only the architecture label inside the private dkms tree changes; the built .ko is found recursively under the kernel directory, so the produced packages are identical. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
The DKMS-built .ko was installed into linux-image with `install -m 644` and never stripped. dh_strip skips /lib/modules, and the module never flows through `make modules_install`, which strips in-tree modules via INSTALL_MOD_STRIP=1. So an unstripped ~2.8 MB module shipped on every device, with its debug symbols baked in rather than separated out. Give each bundled .ko the same two-stage treatment in-tree modules get: capture its debug into the -dbg package's parallel debug tree with objcopy --only-keep-debug (matching the in-tree modules.order loop), then strip the shipped copy in place with strip --strip-debug. That is the exact tool and flag modules_install INSTALL_MOD_STRIP=1 uses on in-tree modules (the kernel's STRIP --strip-debug, plain strip on native arm64). --strip-debug is required for kernel modules (a full strip drops the symtab and relocations needed to load), and the strip must follow the debug capture. Loadability is unaffected (depmod/modinfo data preserved). Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
Replace the dpkg-query version parse (cut -d- mishandles epochs and hyphenated upstream versions) with resolution through the package manager: dpkg -L <name>-dkms names the one /usr/src/<dir>/dkms.conf the package ships (dh_dkms layout), so the source tree is found without guessing and a look-alike directory from another package, for example a future kgsl-extras-dkms next to kgsl-dkms, can never be picked up. The dkms name/version tokens are then read from dkms.conf (PACKAGE_NAME/PACKAGE_VERSION), which is what dkms itself keys its tree layout and build on, so the manifest entry only needs to match the package name, not the module's internal naming. Guards fail the build when the package is missing, ships zero or several dkms.conf files, lacks the name/version fields, or when two manifest entries resolve to the same dkms module. No change to the produced packages: for kgsl the resolved source dir, tokens, and build call are identical. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
A missing module can be a failed build or a skipped one, and dkms
exit-code conventions are not a reliable discriminator (a
BUILD_EXCLUSIVE skip can exit 0, and conventions vary across dkms
versions). Worse, on a real compile failure dkms points at its
make.log inside the private dkms tree, which lives in an ephemeral
build environment: the actual compile error never reached the CI log.
Capture the dkms exit code and judge the outcome by artifacts:
- a .ko under <tree>/<name>/<ver>/<kernel>/ means success;
- otherwise, make.log present means a build was attempted and
failed: print its tail inline (the only surviving record in CI),
together with the exit code;
- no make.log means dkms attempted no build: print the
BUILD_EXCLUSIVE gates declared in the module's dkms.conf, evaluate
each BUILD_EXCLUSIVE_CONFIG against the staged kernel config, and
print the kernel release and dkms arch being built, so the
unsatisfied gate is visible without reproducing the build;
- call out compressed module output explicitly if that is what the
build produced, since only plain .ko is bundled.
The build still hard-fails in every no-module case (a manifest entry
is a presence contract). The private dkms tree is now removed via an
EXIT trap, so failure paths no longer leak it.
Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
Two manifest entries emitting a .ko with the same basename would silently overwrite each other in extra/, and a bundled module sharing its name with an in-tree module would ship two copies of the same module name in one package, leaving precedence on the target to depmod search order. Fail the build loudly in both cases; neither is a configuration this package should ship. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
…les.sh
Move the entire out-of-tree DKMS module integration block out of
override_dh_auto_install in debian/rules and into a dedicated, standalone
tool: debian/scripts/bundle-dkms-modules.sh.
Motivation
----------
The DKMS block in debian/rules had grown to ~120 lines of dense shell
embedded in Make recipe syntax. Keeping it there made debian/rules harder
to read, and any future DKMS-related change (new module type, signing,
compression support) required editing the build rules file directly.
Extracting the logic into a named tool with a documented CLI achieves three
goals simultaneously:
1. Cleanliness — debian/rules is reduced to a single 7-line invocation
with explicit, named parameters; the build rules file describes *what*
happens, not *how* DKMS modules are built.
2. Modularity — all DKMS logic lives in one place. Future changes
(additional guards, new dkms.conf fields, signing hooks) touch only
bundle-dkms-modules.sh, never debian/rules.
3. Developer ergonomics — the script is independently invocable. A
developer who has already staged the kernel trees can re-run just the
DKMS bundling step without re-executing the full dpkg-buildpackage,
passing the required paths explicitly via a documented CLI.
What the script does (unchanged from the original rules block)
--------------------------------------------------------------
For each module listed in debian/dkms-modules:
- Resolves the installed -dkms package via 'dpkg -L' (authoritative;
no /usr/src/ globbing; a look-alike from another package cannot be
picked up).
- Reads PACKAGE_NAME / PACKAGE_VERSION from the package's dkms.conf
(the authority on the tokens that drive the dkms tree layout).
- Builds with 'dkms build' against the staged kernel headers, using a
private --dkmstree (mktemp -d) to avoid writing to the root-owned
/var/lib/dkms/ under fakeroot.
- Judges the outcome by .ko artifact presence, not dkms exit code
(conventions vary across dkms versions; a BUILD_EXCLUSIVE skip can
exit 0). On failure: prints make.log tail (compile error) or
BUILD_EXCLUSIVE gate analysis with per-CONFIG_* kernel config
evaluation (skip), then hard-fails — a manifest entry is a presence
contract.
- For each produced .ko: collision-checks against already-bundled
modules (duplicate basename) and in-tree modules (depmod precedence
ambiguity); installs to lib/modules/<kver>/extra/; extracts debug
symbols via 'objcopy --only-keep-debug' into the -dbg staging tree;
strips the shipped copy with 'strip --strip-debug' (not full strip:
kernel modules require their symtab and relocations to be loadable
by the module loader).
One intentional improvement over the original rules block: the .ko
install loop is rewritten from 'printf | while' (pipe subshell) to
'while < <(process substitution)', so 'exit 1' inside the collision
guards terminates the script directly rather than only the subshell.
Under 'set -e -o pipefail' both forms propagate failure, but the process
substitution form is unambiguous and does not rely on pipefail semantics.
CLI design
----------
Required parameters (must be passed explicitly; no defaults derivable
without Make context):
--kver KVER Kernel release string (uname -r)
--headers-dir DIR Absolute path to staged kernel headers root
(MUST be absolute: dkms cd's into the module
source dir before invoking make)
--image-pkg-dir DIR linux-image staging tree root
--dbg-pkg-dir DIR linux-image-dbg staging tree root
Optional parameters (have safe defaults matching the rules context):
--modules-manifest FILE Default: debian/dkms-modules (relative to
the script's own location)
--arch ARCH Default: aarch64 (uname -m vocabulary for
dkms --arch and BUILD_EXCLUSIVE_ARCH gates)
--objcopy PATH Default: aarch64-linux-gnu-objcopy -> objcopy
-h, --help Full usage with prerequisites and examples
debian/rules changes
--------------------
- Add DKMS_ARCH ?= aarch64 Make variable (passed to script via --arch;
kept in rules so it remains overridable at dpkg-buildpackage time via
the standard Make variable override mechanism).
- Replace the ~120-line DKMS shell block with a 7-line script
invocation. All paths are passed as absolute via $(CURDIR) to satisfy
the --headers-dir absoluteness requirement and to make the invocation
self-documenting.
- Remove the DKMS_MODULES Make variable (manifest is now read at
runtime by the script; no Make-time evaluation needed).
New files
---------
debian/scripts/bundle-dkms-modules.sh standalone DKMS bundling tool
debian/dkms-modules module manifest (kgsl)
Modified files
--------------
debian/rules DKMS_ARCH var + single script invocation
debian/control.in kgsl-dkms (>= 1.0.2), dkms in Build-Depends
README.md repo structure, file table, new DKMS bundling section
Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
The DKMS extraction (14a1f96) left a stray "; \" line between the vmlinux block's closing "fi; \" and the bundle-dkms-modules.sh invocation. Under .ONESHELL (SHELL=/bin/bash, .SHELLFLAGS=-e -o pipefail -c) the whole recipe is one spliced `bash -c`, so the backslash-continuations collapse the region to "...fi; ; <script>", an empty command between two ";" separators. bash rejects it with "syntax error near unexpected token ';'", and the recipe aborts at parse time before the DKMS script (or anything after the vmlinux block) ever runs. This is an unconditional build break: CI run 28134406848 failed at "debian/rules:211: override_dh_auto_install" with exactly this error after a full 12-minute kernel compile, and the .deb was never built. Remove the stray separator; the preceding "fi; \" already terminates the if-statement. Verified with a make .ONESHELL reproduction (errors before, parses and reaches the script after) and bash -n on the recipe. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
The manifest is a presence contract, but a missing or misdirected --modules-manifest produced an empty module list, which the script treated as "no modules" and exited 0 — silently shipping a kernel without its declared out-of-tree modules. That is exactly the failure the contract exists to prevent. Check the manifest exists before parsing and hard-fail if it does not; only an existing-but-empty (or comment-only) manifest is a legitimate skip. This also removes the now-dead post-parse existence check and drops the cd error-suppression in the path-to-absolute resolution (the file is confirmed present first, so the cd cannot fail and a mangled path can no longer slip through). Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
…uirement
For the standalone developer path the CLI was too permissive:
- An unknown/misspelled option fell into usage(), which exits 0, so a
typo'd flag looked like success. Report it on stderr and exit 1.
- A value-taking flag given as the last argument tripped `set -u` with
a raw "$2: unbound variable". Guard each with require_val so it fails
with a clear "<flag> requires a value" message.
Also document two prerequisites that were implicit: module resolution
needs a Debian-family host with a dpkg database, and the script does not
cross-compile (dkms uses the host toolchain), so it must run on a native
arm64 host for the produced .ko to match the target kernel. --arch only
sets the dkms BUILD_EXCLUSIVE_ARCH label, not the compiler.
Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
Bjordis Collaku (bjordiscollaku)
force-pushed
the
feat/dkms-build-time-module-integration
branch
from
June 25, 2026 18:49
1d842df to
916da65
Compare
4 tasks
Bjordis Collaku (bjordiscollaku)
added a commit
that referenced
this pull request
Jul 8, 2026
The Debian Debusine build path now needs kgsl-dkms as a build dependency (via PR #54 against qcom/debian/latest). The package is published in the qli Debusine workspace, so the build step must expose qli as an extra build-dependency source when invoking debusine-action/lib/build. Set EXTRA_BUILD_DEP_WORKSPACES=qli in Build in Debusine and pass DEBUSINE_USER, which lib/build currently requires to apply its extra_repositories compatibility workaround.
Bjordis Collaku (bjordiscollaku)
added a commit
that referenced
this pull request
Jul 8, 2026
The Debian Debusine build path now needs kgsl-dkms as a build dependency (via PR #54 against qcom/debian/latest). The package is published in the qli Debusine workspace, so the build step must expose qli as an extra build-dependency source when invoking debusine-action/lib/build. Set EXTRA_BUILD_DEP_WORKSPACES=qli in Build in Debusine and pass DEBUSINE_USER, which lib/build currently requires to apply its extra_repositories compatibility workaround. Signed-off-by: Bjordis Collaku <bcollaku@qti.qualcomm.com>
Bjordis Collaku (bjordiscollaku)
force-pushed
the
feat/dkms-build-time-module-integration
branch
from
July 11, 2026 00:22
9917688 to
916da65
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds a build-time mechanism that compiles out-of-tree kernel modules from
their DKMS source packages and bundles them into the kernel package, in the same
run that builds the kernel. The device receives one self-contained kernel
package: the module is present and pinned to the exact kernel it was built
against, with no DKMS, compiler, or headers required on the target.
The mechanism is module-agnostic. It is implemented as a standalone, documented
tool (
debian/scripts/bundle-dkms-modules.sh) thatdebian/rulesinvokes duringthe build, and it is driven by a manifest, so onboarding any out-of-tree module
is a two-line change with no change to the build logic.
Design history and rationale: #44.
How it works, in order
flowchart TD P["module DKMS source package<br/>(from the distro apt source)"] K["1. Build kernel + headers<br/>in one packaging run"] H["2. Stage headers in the build tree"] R["3. debian/rules calls<br/>bundle-dkms-modules.sh"] RES["4. Resolve source + tokens<br/>via dpkg -L and dkms.conf"] D["5. dkms build against the<br/>staged headers (private tree)"] J{"6. Built a .ko?"} X["Fail the build<br/>with precise diagnostics"] S["7. Extract debug to -dbg,<br/>strip the shipped copy"] B["8. Bundle into the kernel image<br/>under lib/modules/KVER/extra"] O["9. On device: depmod indexes it,<br/>modprobe + autoload work"] P -. "Build-Depends" .-> RES K --> H --> R --> RES --> D --> J J -- "no" --> X J -- "yes" --> S --> B --> O*-dkmspackage is installed viaBuild-Depends(build-timeonly; it never enters any binary package's runtime dependencies).
debian/rules(override_dh_auto_install) invokes the bundling tool once thetrees are staged.
name/version tokens from
dpkg -Land the shippeddkms.conf.dkms buildagainst the staged headers in a private DKMS tree, so themodule matches the exact kernel and the build stays rootless under
fakeroot.build with precise diagnostics if a listed module produces nothing.
-dbgpackage and strips the shippedcopy, the same two-stage handling an in-tree module gets.
lib/modules/<KVER>/extra/,guarding against duplicate and in-tree name collisions.
depmodindexesextra/, somodprobeand autoload resolve the module.
The tool:
debian/scripts/bundle-dkms-modules.shThe bundling logic is a single standalone tool with a documented CLI, so
debian/rulesstays a thin call, future changes (a new guard, signing,compression) touch one file, and a developer can re-run just the bundling step
against already-staged trees.
The tool resolves each module authoritatively (no path globbing), judges
outcomes by artifacts, hard-fails when a declared module produces nothing, prints actionable diagnostics on failure
(the
make.logtail for a build failure, or thedkms.confgates evaluatedagainst the kernel config for a skip), and guards against name collisions.
CI path:
debian/rulescalls the tooloverride_dh_auto_installinvokes the tool with explicit, absolute paths afterthe kernel, headers, and debug trees are staged:
$(CURDIR)/debian/scripts/bundle-dkms-modules.sh \ --kver "$$BASE" \ --headers-dir "$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE" \ --image-pkg-dir "$(CURDIR)/$$PKG" \ --dbg-pkg-dir "$(CURDIR)/$$DBG_PKG" \ --arch "$(DKMS_ARCH)" \ --objcopy "$(OBJCOPY)" \ --modules-manifest "$(CURDIR)/debian/dkms-modules"dh_installmodules(called inoverride_dh_installdeb) wires the postinstdepmodthat indexesextra/on the device.Developer path: run the tool standalone
A developer who has already staged the kernel trees can re-run just the bundling
step, without re-running the whole
dpkg-buildpackage:--helpdocuments the full CLI, prerequisites, and a host-arch note (the toolbuilds with the host toolchain and is intended to run on a native arm64 host).
The manifest:
debian/dkms-modulesA plain-text list of module names, one per line. Comments, blank lines, and CRLF
endings are tolerated. This is the single place that decides which modules are
built and bundled; the tool and
debian/rulesare untouched when it changes.Adding a module is two lines:
debian/dkms-modules.dkmsand<name>-dkms (>= x.y.z)toBuild-Dependsindebian/control.in.Module load policy
Blacklisting and load policy are orthogonal to this change. The bundled module is
installed under
/lib/modules/<KVER>/extra/and indexed by the standarddepmod,so to the loader it is an ordinary module: the usual controls apply by module name,
unchanged (a
blacklistorinstallline inmodprobe.d, ormodprobe.blacklist=or
module_blacklist=on the kernel command line). The kernel package intentionallyships no load policy of its own, because a blacklist is board and policy specific
while this package is shared across boards; that decision stays external to this
mechanism, at the image build stage.
Validation
CI (Ubuntu, resolute). Run
28193902324
(resolute, kernel
7.1.0-rc7-qcom-next-20260618). TheBuild kernel packagestep builds and bundles the module from its
*-dkmspackage:The packaged contents confirm the two-stage handling landed in both packages:
All four packages (image, headers, dbg, dbgsym) built, and the
Upload .deb packages to S3step now passes, so every step in the run isgreen and the artifacts are published to
s3://.../pkg/temp/pkg-linux-qcom/28193902324-1/.CI (Debian, trixie). Run
29125019996
(trixie, via Debusine). With
kgsl-dkmsnow published to the Debusine workspace,the Debian path satisfies the
Build-Depends, the build runs to completion, andmsm_kgsl.kois bundled into the linux-image exactly as on the Ubuntu path. AllDebusine build steps pass and the
.debs publish to S3. Both distro families nowbuild and bundle the module end to end: Ubuntu (resolute, docker) and Debian
(trixie, Debusine).
Local (standalone tool, 18 of 18). The tool was exercised through its CLI
against shimmed leaf tools, so its real control flow, resolution, outcome
judgment, collision guards, and path layout all run:
extra/, debug written to the-dbgtree, strip ran on the shipped copydkms.conf, missingPACKAGE_NAMEmake.logtail; gated skip with config set, with config unset, and with no gates declared; compressed output reportedRequirements (R1 to R7)
Build-Dependson the DKMS source packages. DKMS is a build-time tool, not a runtime mechanism.Depends,Recommends, orSuggestsof any binary package.*-dkmspackage. The kernel CI only consumes it.-dbgpackage.Design decisions
debian/rulesis a thin call, future changes touch one place, and the step is independently re-runnable.make.logand thedkms.confgates.dpkg -Lnames the onedkms.confthe package ships; itsPACKAGE_NAME/PACKAGE_VERSIONare the tokens dkms keys on. No path globbing.fakeroot, away from the root-owned/var/lib/dkms.-dbgpackage viaobjcopy --only-keep-debug, then strip the shipped copy viastrip --strip-debug.Commits (13)
Not in this PR
module loads and functions (
modprobe, autoload). Hardware validation, owned bythe device team.
Notes
qcom/debian/latest.