From 51f58594eee3e9940bd9b9fa8a1ebb7cf04c3977 Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Mon, 1 Jun 2026 12:59:18 -0700 Subject: [PATCH 01/13] debian/control.in: declare kgsl-dkms as a build-time dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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-/. 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 --- debian/control.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control.in b/debian/control.in index 46075c7..bd9a857 100644 --- a/debian/control.in +++ b/debian/control.in @@ -5,7 +5,8 @@ Maintainer: Bjordis Collaku Standards-Version: 4.6.2 Build-Depends: debhelper-compat (= 13), bc, bison, flex, libssl-dev, libelf-dev, dwarves, - python3, kmod, cpio, rsync, pkg-config, gcc, make + python3, kmod, cpio, rsync, pkg-config, gcc, make, + kgsl-dkms (>= 1.0.2), dkms Homepage: https://kernel.org Rules-Requires-Root: binary-targets From 08c7a691440b528b005cb72baaeb8c18f569424d Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Mon, 8 Jun 2026 15:56:47 -0700 Subject: [PATCH 02/13] debian/rules: build the out-of-tree DKMS module and bundle it into linux-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//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 --- debian/rules | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 8930339..173ab6e 100755 --- a/debian/rules +++ b/debian/rules @@ -374,7 +374,39 @@ override_dh_auto_install: $$DBG_PKG/usr/lib/debug/vmlinux-$$BASE; \ else \ echo "Warning: vmlinux not found at $$VMLINUX_PATH — debug package will not contain vmlinux"; \ - fi + fi; \ + \ + # ── DKMS module integration ────────────────────────────────────────────────── + # kgsl-dkms is a Build-Depends: dpkg-buildpackage installs it before any + # debian/rules target runs, placing the KGSL source at /usr/src/kgsl-/. + # + # linux-headers are NOT installed on the build host — they are staged at: + # debian/linux-headers--qcom/usr/src/linux-headers-/ + # --kernelsourcedir must point at this staging path; /usr/src/ does not exist + # in the build environment (Docker container or Debusine chroot). + # + # --dkmstree redirects DKMS's working tree away from /var/lib/dkms, which is + # owned by root and not writable by the non-root dpkg-buildpackage process. + KGSL_VER=$$(dpkg-query -W -f='$${Version}' kgsl-dkms | cut -d- -f1); \ + HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ + DKMS_TREE=$$(mktemp -d); \ + mkdir -p "$$DKMS_TREE/kgsl/$$KGSL_VER"; \ + ln -sf "/usr/src/kgsl-$$KGSL_VER" "$$DKMS_TREE/kgsl/$$KGSL_VER/source"; \ + echo "Building DKMS module kgsl/$$KGSL_VER for $$BASE"; \ + echo " kernelsourcedir: $$HDRDIR"; \ + echo " dkmstree: $$DKMS_TREE"; \ + dkms build kgsl/$$KGSL_VER \ + --kernelsourcedir "$$HDRDIR" \ + --dkmstree "$$DKMS_TREE" \ + -k "$$BASE" \ + --arch arm64; \ + find "$$DKMS_TREE/kgsl/$$KGSL_VER/$$BASE" -name '*.ko' | \ + while IFS= read -r ko; do \ + install -D -m 644 "$$ko" \ + "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ + done; \ + rm -rf "$$DKMS_TREE"; \ + echo "Bundled msm_kgsl.ko into debian/linux-image-$$BASE-qcom" # Skip automatic dh_installmodules - we'll call it manually in override_dh_installdeb override_dh_installmodules: From baf0e7976eba2c6393a378491596c6f4914539fe Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:44:09 -0700 Subject: [PATCH 03/13] debian/dkms-modules: drive the bundled modules from a manifest 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 --- debian/dkms-modules | 12 ++++++++ debian/rules | 75 ++++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 debian/dkms-modules diff --git a/debian/dkms-modules b/debian/dkms-modules new file mode 100644 index 0000000..1f0dc21 --- /dev/null +++ b/debian/dkms-modules @@ -0,0 +1,12 @@ +# DKMS modules to build and bundle into linux-image--qcom. +# +# One entry per line: the DKMS package name without the -dkms suffix. +# A corresponding Build-Depends entry must exist in debian/control.in. +# +# To add a module: +# 1. Append the module name here. +# 2. Add -dkms (>= ) to Build-Depends in debian/control.in. +# +# To disable a module temporarily: comment out the line with #. +# debian/rules is untouched in either case. +kgsl \ No newline at end of file diff --git a/debian/rules b/debian/rules index 173ab6e..88c9d45 100755 --- a/debian/rules +++ b/debian/rules @@ -34,6 +34,13 @@ OBJCOPY ?= $(shell which aarch64-linux-gnu-objcopy 2>/dev/null || which objcopy # File that caches the kernelrelease (BASE, i.e., uname -r) KREL_FILE := debian/kernel.release +# DKMS modules to build and bundle into linux-image--qcom. +# Driven by debian/dkms-modules: comments (# to end of line), blank lines, and +# CR line endings are stripped at make evaluation time so the shell loop +# receives a clean space-separated list of module names. +DKMS_MODULES := $(strip $(shell sed -e 's/\#.*//' -e 's/\r$$//' debian/dkms-modules 2>/dev/null \ + | tr -s ' \t\n' ' ')) + %: dh $@ --buildsystem=makefile @@ -377,36 +384,48 @@ override_dh_auto_install: fi; \ \ # ── DKMS module integration ────────────────────────────────────────────────── - # kgsl-dkms is a Build-Depends: dpkg-buildpackage installs it before any - # debian/rules target runs, placing the KGSL source at /usr/src/kgsl-/. - # - # linux-headers are NOT installed on the build host — they are staged at: - # debian/linux-headers--qcom/usr/src/linux-headers-/ - # --kernelsourcedir must point at this staging path; /usr/src/ does not exist - # in the build environment (Docker container or Debusine chroot). + # Module list is read from debian/dkms-modules at Make evaluation time. + # Each listed module is built against the staging headers produced earlier in + # this same dpkg-buildpackage run and its .ko files are bundled into the + # linux-image package under /lib/modules//extra/. # - # --dkmstree redirects DKMS's working tree away from /var/lib/dkms, which is - # owned by root and not writable by the non-root dpkg-buildpackage process. - KGSL_VER=$$(dpkg-query -W -f='$${Version}' kgsl-dkms | cut -d- -f1); \ - HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ - DKMS_TREE=$$(mktemp -d); \ - mkdir -p "$$DKMS_TREE/kgsl/$$KGSL_VER"; \ - ln -sf "/usr/src/kgsl-$$KGSL_VER" "$$DKMS_TREE/kgsl/$$KGSL_VER/source"; \ - echo "Building DKMS module kgsl/$$KGSL_VER for $$BASE"; \ - echo " kernelsourcedir: $$HDRDIR"; \ - echo " dkmstree: $$DKMS_TREE"; \ - dkms build kgsl/$$KGSL_VER \ - --kernelsourcedir "$$HDRDIR" \ - --dkmstree "$$DKMS_TREE" \ - -k "$$BASE" \ - --arch arm64; \ - find "$$DKMS_TREE/kgsl/$$KGSL_VER/$$BASE" -name '*.ko' | \ - while IFS= read -r ko; do \ - install -D -m 644 "$$ko" \ - "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ + # Key constraints: + # --kernelsourcedir Absolute path via $(CURDIR): DKMS invokes make from + # /usr/src/-/ so a relative path resolves to + # nothing from that directory. + # --dkmstree Redirects DKMS artifacts away from /var/lib/dkms/ + # (root-owned) to a per-build mktemp directory shared + # across all modules and removed on completion. + # source symlink Each module's source is at /usr/src/-/ + # (installed by -dkms); a symlink is created in + # the DKMS tree before each build to satisfy DKMS's + # expected tree layout. + if [ -n "$(DKMS_MODULES)" ]; then \ + HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ + DKMS_TREE=$$(mktemp -d); \ + for name in $(DKMS_MODULES); do \ + VER=$$(dpkg-query -W -f='$${Version}' "$${name}-dkms" | cut -d- -f1); \ + mkdir -p "$$DKMS_TREE/$${name}/$$VER"; \ + ln -sf "/usr/src/$${name}-$$VER" "$$DKMS_TREE/$${name}/$$VER/source"; \ + echo "Building DKMS module $${name}/$$VER for $$BASE"; \ + echo " kernelsourcedir: $$HDRDIR"; \ + echo " dkmstree: $$DKMS_TREE"; \ + dkms build "$${name}/$$VER" \ + --kernelsourcedir "$$HDRDIR" \ + --dkmstree "$$DKMS_TREE" \ + -k "$$BASE" \ + --arch arm64; \ + find "$$DKMS_TREE/$${name}/$$VER/$$BASE" -name '*.ko' | \ + while IFS= read -r ko; do \ + install -D -m 644 "$$ko" \ + "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ + done; \ + echo "Bundled $${name} modules into debian/linux-image-$$BASE-qcom"; \ done; \ - rm -rf "$$DKMS_TREE"; \ - echo "Bundled msm_kgsl.ko into debian/linux-image-$$BASE-qcom" + rm -rf "$$DKMS_TREE"; \ + else \ + echo "No DKMS modules listed in debian/dkms-modules — skipping"; \ + fi # Skip automatic dh_installmodules - we'll call it manually in override_dh_installdeb override_dh_installmodules: From 66a74cd2f8bc783ec564df1cfc4328b1592b525b Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:44:47 -0700 Subject: [PATCH 04/13] debian/rules: fail the build when a dkms module yields no .ko 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 --- debian/rules | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/debian/rules b/debian/rules index 88c9d45..caa7058 100755 --- a/debian/rules +++ b/debian/rules @@ -415,11 +415,16 @@ override_dh_auto_install: --dkmstree "$$DKMS_TREE" \ -k "$$BASE" \ --arch arm64; \ - find "$$DKMS_TREE/$${name}/$$VER/$$BASE" -name '*.ko' | \ - while IFS= read -r ko; do \ - install -D -m 644 "$$ko" \ - "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ - done; \ + kos=$$(find "$$DKMS_TREE/$${name}/$$VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ + if [ -z "$$kos" ]; then \ + echo "ERROR: $${name}/$$VER produced no .ko for kernel $$BASE." >&2; \ + echo " Refusing to ship linux-image-$$BASE-qcom without $${name}." >&2; \ + exit 1; \ + fi; \ + printf '%s\n' "$$kos" | while IFS= read -r ko; do \ + install -D -m 644 "$$ko" \ + "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ + done; \ echo "Bundled $${name} modules into debian/linux-image-$$BASE-qcom"; \ done; \ rm -rf "$$DKMS_TREE"; \ From fd61861eb9132fe71bdae5ec68fccb1d76ef00ff Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:45:28 -0700 Subject: [PATCH 05/13] debian/rules: pass dkms the uname -m architecture 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 --- debian/rules | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index caa7058..3f952bd 100755 --- a/debian/rules +++ b/debian/rules @@ -30,6 +30,11 @@ PARALLEL ?= $(shell nproc) # Prefer the aarch64 cross-compiler's objcopy; fall back to the host objcopy. OBJCOPY ?= $(shell which aarch64-linux-gnu-objcopy 2>/dev/null || which objcopy 2>/dev/null || echo objcopy) +# dkms identifies architectures in uname -m vocabulary (aarch64), unlike the +# kbuild ARCH token above (arm64). The distinction matters for dkms.conf +# BUILD_EXCLUSIVE_ARCH gates, which match against this value. +DKMS_ARCH ?= aarch64 + # File that caches the kernelrelease (BASE, i.e., uname -r) KREL_FILE := debian/kernel.release @@ -414,7 +419,7 @@ override_dh_auto_install: --kernelsourcedir "$$HDRDIR" \ --dkmstree "$$DKMS_TREE" \ -k "$$BASE" \ - --arch arm64; \ + --arch $(DKMS_ARCH); \ kos=$$(find "$$DKMS_TREE/$${name}/$$VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ if [ -z "$$kos" ]; then \ echo "ERROR: $${name}/$$VER produced no .ko for kernel $$BASE." >&2; \ From 8dbfa097e4d66992590ab74b2a44e7e886e5fe95 Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Thu, 4 Jun 2026 17:09:21 -0700 Subject: [PATCH 06/13] debian/rules: strip bundled DKMS modules and capture their debug 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 --- debian/rules | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/debian/rules b/debian/rules index 3f952bd..3491421 100755 --- a/debian/rules +++ b/debian/rules @@ -426,9 +426,29 @@ override_dh_auto_install: echo " Refusing to ship linux-image-$$BASE-qcom without $${name}." >&2; \ exit 1; \ fi; \ + # Mirror the in-tree module treatment for each bundled .ko. In-tree, the + # unstripped build-tree copy is the debug donor while modules_install + # writes a separately stripped copy (INSTALL_MOD_STRIP=1, i.e. STRIP + # --strip-debug). We have only the one dkms-built .ko, so both roles are + # performed on it, in order: + # Stage 1 — copy its debug into the -dbg package's parallel debug tree + # (same path layout as the in-tree extraction above); a + # non-destructive read, so the shipped copy is untouched. + # Stage 2 — strip the shipped copy in place with `strip --strip-debug`, + # matching exactly what modules_install INSTALL_MOD_STRIP=1 + # runs on the in-tree modules (the kernel's STRIP --strip-debug; + # plain strip on this native arm64 build). --strip-debug is + # required for kernel modules — a full strip drops the symtab + # and relocations needed to load. Must follow stage 1. (strip, + # not objcopy: objcopy's single-arg in-place form no-op'd here.) printf '%s\n' "$$kos" | while IFS= read -r ko; do \ - install -D -m 644 "$$ko" \ - "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$(basename $$ko)"; \ + b=$$(basename "$$ko"); \ + dest="debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$b"; \ + dbg="$$DBG_PKG/usr/lib/debug/lib/modules/$$BASE/extra/$$b"; \ + install -D -m 644 "$$ko" "$$dest"; \ + mkdir -p "$${dbg%/*}"; \ + $(OBJCOPY) --only-keep-debug "$$dest" "$$dbg" 2>/dev/null || cp -a "$$dest" "$$dbg"; \ + strip --strip-debug "$$dest"; \ done; \ echo "Bundled $${name} modules into debian/linux-image-$$BASE-qcom"; \ done; \ From dfba77c6ffe8c261934ae83995b847aef32340ea Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:47:07 -0700 Subject: [PATCH 07/13] debian/rules: resolve DKMS modules via dpkg and dkms.conf Replace the dpkg-query version parse (cut -d- mishandles epochs and hyphenated upstream versions) with resolution through the package manager: dpkg -L -dkms names the one /usr/src//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 --- debian/rules | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/debian/rules b/debian/rules index 3491421..254ff43 100755 --- a/debian/rules +++ b/debian/rules @@ -409,21 +409,36 @@ override_dh_auto_install: HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ DKMS_TREE=$$(mktemp -d); \ for name in $(DKMS_MODULES); do \ - VER=$$(dpkg-query -W -f='$${Version}' "$${name}-dkms" | cut -d- -f1); \ - mkdir -p "$$DKMS_TREE/$${name}/$$VER"; \ - ln -sf "/usr/src/$${name}-$$VER" "$$DKMS_TREE/$${name}/$$VER/source"; \ - echo "Building DKMS module $${name}/$$VER for $$BASE"; \ + # Resolve the module from the package manager rather than by globbing + # /usr/src: -dkms ships exactly one /usr/src//dkms.conf + # (dh_dkms layout) and dpkg -L is authoritative about where, so a + # look-alike directory from another package can never be picked up. + # dkms.conf is then the authority on the dkms name/version tokens + # (they drive the dkms tree layout and the build call); its values + # are read literally, as installed by dh_dkms. + conf=$$(dpkg -L "$${name}-dkms" 2>/dev/null | grep -E '^/usr/src/[^/]+/dkms\.conf$$' || true); \ + [ -n "$$conf" ] || { echo "ERROR: $${name}-dkms is not installed or ships no /usr/src//dkms.conf (is it in Build-Depends?)" >&2; exit 1; }; \ + [ $$(printf '%s\n' "$$conf" | wc -l) -eq 1 ] || { echo "ERROR: $${name}-dkms ships multiple dkms.conf files:" >&2; printf '%s\n' "$$conf" >&2; exit 1; }; \ + SRC=$${conf%/dkms.conf}; \ + PKG_NAME=$$(sed -n 's/^[[:space:]]*PACKAGE_NAME=//p' "$$conf" | tail -1 | tr -d '"'); \ + PKG_VER=$$(sed -n 's/^[[:space:]]*PACKAGE_VERSION=//p' "$$conf" | tail -1 | tr -d '"'); \ + { [ -n "$$PKG_NAME" ] && [ -n "$$PKG_VER" ]; } || { echo "ERROR: PACKAGE_NAME/PACKAGE_VERSION not found in $$conf" >&2; exit 1; }; \ + [ ! -e "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER" ] || { echo "ERROR: duplicate dkms module $$PKG_NAME/$$PKG_VER (already prepared by an earlier manifest entry)" >&2; exit 1; }; \ + mkdir -p "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER"; \ + ln -sf "$$SRC" "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/source"; \ + echo "Building DKMS module $$PKG_NAME/$$PKG_VER for $$BASE"; \ + echo " source: $$SRC"; \ echo " kernelsourcedir: $$HDRDIR"; \ echo " dkmstree: $$DKMS_TREE"; \ - dkms build "$${name}/$$VER" \ + dkms build "$$PKG_NAME/$$PKG_VER" \ --kernelsourcedir "$$HDRDIR" \ --dkmstree "$$DKMS_TREE" \ -k "$$BASE" \ --arch $(DKMS_ARCH); \ - kos=$$(find "$$DKMS_TREE/$${name}/$$VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ + kos=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ if [ -z "$$kos" ]; then \ - echo "ERROR: $${name}/$$VER produced no .ko for kernel $$BASE." >&2; \ - echo " Refusing to ship linux-image-$$BASE-qcom without $${name}." >&2; \ + echo "ERROR: $$PKG_NAME/$$PKG_VER produced no .ko for kernel $$BASE." >&2; \ + echo " Refusing to ship linux-image-$$BASE-qcom without $$PKG_NAME." >&2; \ exit 1; \ fi; \ # Mirror the in-tree module treatment for each bundled .ko. In-tree, the @@ -450,7 +465,7 @@ override_dh_auto_install: $(OBJCOPY) --only-keep-debug "$$dest" "$$dbg" 2>/dev/null || cp -a "$$dest" "$$dbg"; \ strip --strip-debug "$$dest"; \ done; \ - echo "Bundled $${name} modules into debian/linux-image-$$BASE-qcom"; \ + echo "Bundled $$PKG_NAME modules into debian/linux-image-$$BASE-qcom"; \ done; \ rm -rf "$$DKMS_TREE"; \ else \ From e76e9cea59d8aedcab7659ac536ef56be94d7620 Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:48:09 -0700 Subject: [PATCH 08/13] debian/rules: diagnose dkms build outcomes precisely 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 //// 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 --- debian/rules | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/debian/rules b/debian/rules index 254ff43..16bbde0 100755 --- a/debian/rules +++ b/debian/rules @@ -408,6 +408,7 @@ override_dh_auto_install: if [ -n "$(DKMS_MODULES)" ]; then \ HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ DKMS_TREE=$$(mktemp -d); \ + trap 'rm -rf "$$DKMS_TREE"' EXIT; \ for name in $(DKMS_MODULES); do \ # Resolve the module from the package manager rather than by globbing # /usr/src: -dkms ships exactly one /usr/src//dkms.conf @@ -430,15 +431,46 @@ override_dh_auto_install: echo " source: $$SRC"; \ echo " kernelsourcedir: $$HDRDIR"; \ echo " dkmstree: $$DKMS_TREE"; \ + dkms_rc=0; \ dkms build "$$PKG_NAME/$$PKG_VER" \ --kernelsourcedir "$$HDRDIR" \ --dkmstree "$$DKMS_TREE" \ -k "$$BASE" \ - --arch $(DKMS_ARCH); \ + --arch $(DKMS_ARCH) || dkms_rc=$$?; \ + # Judge the outcome by artifacts, not by dkms exit-code conventions + # (those vary across dkms versions; a skip can exit 0): a .ko under + # //// means success. Otherwise dkms's own + # make.log separates the two failure modes: it exists only when a + # build was attempted (and failed); a BUILD_EXCLUSIVE skip never + # creates it. The log is printed inline because the private dkms + # tree is deleted on exit, so it is the only surviving record in CI. kos=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ - if [ -z "$$kos" ]; then \ - echo "ERROR: $$PKG_NAME/$$PKG_VER produced no .ko for kernel $$BASE." >&2; \ - echo " Refusing to ship linux-image-$$BASE-qcom without $$PKG_NAME." >&2; \ + if [ "$$dkms_rc" -ne 0 ] || [ -z "$$kos" ]; then \ + mklog=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER" -name make.log 2>/dev/null | head -1 || true); \ + if [ -n "$$mklog" ]; then \ + echo "ERROR: dkms build failed for $$PKG_NAME/$$PKG_VER on kernel $$BASE (dkms exit $$dkms_rc); make.log tail:" >&2; \ + tail -n 300 "$$mklog" | sed 's/^/ | /' >&2; \ + else \ + echo "ERROR: $$PKG_NAME/$$PKG_VER produced no module for kernel $$BASE; dkms attempted no build (dkms exit $$dkms_rc)." >&2; \ + gates=$$(grep -E '^[[:space:]]*BUILD_EXCLUSIVE' "$$conf" 2>/dev/null || true); \ + if [ -n "$$gates" ]; then \ + echo " BUILD_EXCLUSIVE gates declared by $$conf:" >&2; \ + printf '%s\n' "$$gates" | sed 's/^[[:space:]]*/ | /' >&2; \ + for c in $$(printf '%s\n' "$$gates" | sed -n 's/^[[:space:]]*BUILD_EXCLUSIVE_CONFIG=//p' | tr -d '"'); do \ + if grep -q "^$$c=[ym]" "$$PKG/boot/config-$$BASE" 2>/dev/null; then \ + echo " | $$c is set in this kernel's config" >&2; \ + else \ + echo " | $$c is NOT set in this kernel's config" >&2; \ + fi; \ + done; \ + echo " This kernel: $$BASE, dkms arch $(DKMS_ARCH)." >&2; \ + else \ + echo " No BUILD_EXCLUSIVE gates are declared in $$conf; see the dkms output above." >&2; \ + fi; \ + comp=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/$$BASE" \( -name '*.ko.gz' -o -name '*.ko.xz' -o -name '*.ko.zst' \) 2>/dev/null | head -1 || true); \ + [ -z "$$comp" ] || echo " Note: found compressed module output ($$comp); compressed dkms output is not supported." >&2; \ + fi; \ + echo "Refusing to ship linux-image-$$BASE-qcom without $$PKG_NAME." >&2; \ exit 1; \ fi; \ # Mirror the in-tree module treatment for each bundled .ko. In-tree, the @@ -467,9 +499,8 @@ override_dh_auto_install: done; \ echo "Bundled $$PKG_NAME modules into debian/linux-image-$$BASE-qcom"; \ done; \ - rm -rf "$$DKMS_TREE"; \ else \ - echo "No DKMS modules listed in debian/dkms-modules — skipping"; \ + echo "No DKMS modules listed in debian/dkms-modules; skipping"; \ fi # Skip automatic dh_installmodules - we'll call it manually in override_dh_installdeb From 6669ce839f60af1228bba7cfe823b9f46505c352 Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 10 Jun 2026 21:26:41 -0700 Subject: [PATCH 09/13] debian/rules: guard against module name collisions when bundling 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 --- debian/rules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/debian/rules b/debian/rules index 16bbde0..3ed1fc6 100755 --- a/debian/rules +++ b/debian/rules @@ -492,6 +492,9 @@ override_dh_auto_install: b=$$(basename "$$ko"); \ dest="debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$b"; \ dbg="$$DBG_PKG/usr/lib/debug/lib/modules/$$BASE/extra/$$b"; \ + [ ! -e "$$dest" ] || { echo "ERROR: duplicate bundled module name $$b (already bundled by an earlier manifest entry)" >&2; exit 1; }; \ + intree=$$(find "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/kernel" -name "$$b" -print -quit 2>/dev/null || true); \ + [ -z "$$intree" ] || { echo "ERROR: bundled module $$b collides with in-tree $$intree; module precedence on the target would be ambiguous" >&2; exit 1; }; \ install -D -m 644 "$$ko" "$$dest"; \ mkdir -p "$${dbg%/*}"; \ $(OBJCOPY) --only-keep-debug "$$dest" "$$dbg" 2>/dev/null || cp -a "$$dest" "$$dbg"; \ From 1a833dd73e60ff3ccd5e17de331195a3aaf42d2e Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 24 Jun 2026 15:40:43 -0700 Subject: [PATCH 10/13] packaging: extract DKMS bundling into debian/scripts/bundle-dkms-modules.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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//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 --- README.md | 70 ++++ debian/dkms-modules | 4 +- debian/rules | 137 +------- debian/scripts/bundle-dkms-modules.sh | 447 ++++++++++++++++++++++++++ 4 files changed, 533 insertions(+), 125 deletions(-) create mode 100755 debian/scripts/bundle-dkms-modules.sh diff --git a/README.md b/README.md index 01446ea..cf2cde0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ pkg-linux-qcom/ │ │ ├── qcom-imsdk.config ← DMABUF heaps for Qualcomm IMSDK/GStreamer │ │ ├── qemu-boot.config ← virtio drivers for QEMU testing │ │ └── usb-can.config ← USB CAN adapters +│ ├── dkms-modules ← Manifest of out-of-tree modules to bundle at build time +│ └── scripts/ +│ └── bundle-dkms-modules.sh ← DKMS build-and-bundle tool (called by rules; standalone-capable) ├── .gitignore └── README.md ``` @@ -70,6 +73,8 @@ pkg-linux-qcom/ | `debian/linux-image.postrm` | ✅ Committed | Post-remove maintainer script | | `debian/config/*.config` | ✅ Committed | Always-applied config fragments | | `debian/config-available/*.config` | ✅ Committed | Optional fragment library | +| `debian/dkms-modules` | ✅ Committed | Manifest of out-of-tree DKMS modules to bundle | +| `debian/scripts/bundle-dkms-modules.sh` | ✅ Committed | DKMS build-and-bundle tool | | `debian/control` | 🔄 Generated | Produced by `make -f debian/rules prepare KVER=...` | | `debian/changelog` | 🔄 Generated | Produced by `make -f debian/rules prepare KVER=...` | | `debian/kernel.release` | 🔄 Generated | Produced during `dpkg-buildpackage` | @@ -307,6 +312,65 @@ Virtual packages provided: `linux-image-dbg` --- +## DKMS module bundling + +Out-of-tree kernel modules listed in `debian/dkms-modules` are built at +`dpkg-buildpackage` time and bundled directly into `linux-image--qcom`. +The target device receives the pre-built `.ko` without needing a compiler, +kernel headers, or DKMS tooling installed. + +### How it works + +`debian/rules` calls `debian/scripts/bundle-dkms-modules.sh` at the end of +`override_dh_auto_install`, after the kernel image, modules, headers, and debug +packages have been staged. The script: + +1. Reads the module list from `debian/dkms-modules` (one name per line, without + the `-dkms` suffix; comments and blank lines ignored). +2. Resolves each module's source tree via `dpkg -L -dkms` (authoritative; + no `/usr/src/` globbing). +3. Reads `PACKAGE_NAME` / `PACKAGE_VERSION` from the package's `dkms.conf`. +4. Builds with `dkms build` against the staged kernel headers, using a private + `--dkmstree` (`mktemp`) to avoid writing to the root-owned `/var/lib/dkms/`. +5. Judges the outcome by `.ko` artifact presence, not `dkms` exit code. + On failure: prints `make.log` tail (compile error) or `BUILD_EXCLUSIVE` gate + analysis (skip), then hard-fails — a manifest entry is a presence contract. +6. For each produced `.ko`: collision-checks against already-bundled and in-tree + modules; installs to `lib/modules//extra/`; extracts debug symbols via + `objcopy --only-keep-debug` into the `-dbg` package; strips with + `strip --strip-debug` (required for kernel modules — a full strip drops the + symtab and relocations needed by the module loader). + +### `debian/dkms-modules` manifest + +``` +# One module name per line (without the -dkms suffix). +# A corresponding Build-Depends entry must exist in debian/control.in. +kgsl +``` + +To add a module: append its name and add `-dkms (>= )` to +`Build-Depends` in `debian/control.in`. To disable temporarily: comment out +the line. `debian/rules` and `bundle-dkms-modules.sh` are untouched in either case. + +### Standalone developer use + +`bundle-dkms-modules.sh` can be invoked directly after a manual build has staged +the kernel trees, without re-running the full `dpkg-buildpackage`: + +```bash +debian/scripts/bundle-dkms-modules.sh \ + --kver 6.12.0-qcom-next-20260210 \ + --headers-dir /path/to/kernel-source/debian/linux-headers-6.12.0-qcom-next-20260210-qcom/usr/src/linux-headers-6.12.0-qcom-next-20260210 \ + --image-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-qcom \ + --dbg-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-qcom-dbg +``` + +Run `debian/scripts/bundle-dkms-modules.sh --help` for full usage, prerequisites, +and all available options (`--arch`, `--objcopy`, `--modules-manifest`). + +--- + ## The `build` and `source` symlinks `/lib/modules//build` and `/lib/modules//source` are symlinks @@ -481,3 +545,9 @@ Merges `arch/arm64/configs/debug.config` from the kernel source if present. | Ubuntu 26.04 | `resolute` | | | Debian 13 | `trixie` | Default | | Debian unstable | `sid` | | + +--- + +## License + +pkg-linux-qcom is licensed under the BSD-3-Clause License. See [LICENSE.txt](LICENSE.txt) for the full license text. diff --git a/debian/dkms-modules b/debian/dkms-modules index 1f0dc21..1b11c8d 100644 --- a/debian/dkms-modules +++ b/debian/dkms-modules @@ -8,5 +8,5 @@ # 2. Add -dkms (>= ) to Build-Depends in debian/control.in. # # To disable a module temporarily: comment out the line with #. -# debian/rules is untouched in either case. -kgsl \ No newline at end of file +# debian/rules and debian/scripts/bundle-dkms-modules.sh are untouched in either case. +kgsl diff --git a/debian/rules b/debian/rules index 3ed1fc6..0ae866b 100755 --- a/debian/rules +++ b/debian/rules @@ -39,13 +39,6 @@ DKMS_ARCH ?= aarch64 # File that caches the kernelrelease (BASE, i.e., uname -r) KREL_FILE := debian/kernel.release -# DKMS modules to build and bundle into linux-image--qcom. -# Driven by debian/dkms-modules: comments (# to end of line), blank lines, and -# CR line endings are stripped at make evaluation time so the shell loop -# receives a clean space-separated list of module names. -DKMS_MODULES := $(strip $(shell sed -e 's/\#.*//' -e 's/\r$$//' debian/dkms-modules 2>/dev/null \ - | tr -s ' \t\n' ' ')) - %: dh $@ --buildsystem=makefile @@ -387,124 +380,22 @@ override_dh_auto_install: else \ echo "Warning: vmlinux not found at $$VMLINUX_PATH — debug package will not contain vmlinux"; \ fi; \ + ; \ \ # ── DKMS module integration ────────────────────────────────────────────────── - # Module list is read from debian/dkms-modules at Make evaluation time. - # Each listed module is built against the staging headers produced earlier in - # this same dpkg-buildpackage run and its .ko files are bundled into the - # linux-image package under /lib/modules//extra/. - # - # Key constraints: - # --kernelsourcedir Absolute path via $(CURDIR): DKMS invokes make from - # /usr/src/-/ so a relative path resolves to - # nothing from that directory. - # --dkmstree Redirects DKMS artifacts away from /var/lib/dkms/ - # (root-owned) to a per-build mktemp directory shared - # across all modules and removed on completion. - # source symlink Each module's source is at /usr/src/-/ - # (installed by -dkms); a symlink is created in - # the DKMS tree before each build to satisfy DKMS's - # expected tree layout. - if [ -n "$(DKMS_MODULES)" ]; then \ - HDRDIR="$(CURDIR)/debian/linux-headers-$$BASE-qcom/usr/src/linux-headers-$$BASE"; \ - DKMS_TREE=$$(mktemp -d); \ - trap 'rm -rf "$$DKMS_TREE"' EXIT; \ - for name in $(DKMS_MODULES); do \ - # Resolve the module from the package manager rather than by globbing - # /usr/src: -dkms ships exactly one /usr/src//dkms.conf - # (dh_dkms layout) and dpkg -L is authoritative about where, so a - # look-alike directory from another package can never be picked up. - # dkms.conf is then the authority on the dkms name/version tokens - # (they drive the dkms tree layout and the build call); its values - # are read literally, as installed by dh_dkms. - conf=$$(dpkg -L "$${name}-dkms" 2>/dev/null | grep -E '^/usr/src/[^/]+/dkms\.conf$$' || true); \ - [ -n "$$conf" ] || { echo "ERROR: $${name}-dkms is not installed or ships no /usr/src//dkms.conf (is it in Build-Depends?)" >&2; exit 1; }; \ - [ $$(printf '%s\n' "$$conf" | wc -l) -eq 1 ] || { echo "ERROR: $${name}-dkms ships multiple dkms.conf files:" >&2; printf '%s\n' "$$conf" >&2; exit 1; }; \ - SRC=$${conf%/dkms.conf}; \ - PKG_NAME=$$(sed -n 's/^[[:space:]]*PACKAGE_NAME=//p' "$$conf" | tail -1 | tr -d '"'); \ - PKG_VER=$$(sed -n 's/^[[:space:]]*PACKAGE_VERSION=//p' "$$conf" | tail -1 | tr -d '"'); \ - { [ -n "$$PKG_NAME" ] && [ -n "$$PKG_VER" ]; } || { echo "ERROR: PACKAGE_NAME/PACKAGE_VERSION not found in $$conf" >&2; exit 1; }; \ - [ ! -e "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER" ] || { echo "ERROR: duplicate dkms module $$PKG_NAME/$$PKG_VER (already prepared by an earlier manifest entry)" >&2; exit 1; }; \ - mkdir -p "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER"; \ - ln -sf "$$SRC" "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/source"; \ - echo "Building DKMS module $$PKG_NAME/$$PKG_VER for $$BASE"; \ - echo " source: $$SRC"; \ - echo " kernelsourcedir: $$HDRDIR"; \ - echo " dkmstree: $$DKMS_TREE"; \ - dkms_rc=0; \ - dkms build "$$PKG_NAME/$$PKG_VER" \ - --kernelsourcedir "$$HDRDIR" \ - --dkmstree "$$DKMS_TREE" \ - -k "$$BASE" \ - --arch $(DKMS_ARCH) || dkms_rc=$$?; \ - # Judge the outcome by artifacts, not by dkms exit-code conventions - # (those vary across dkms versions; a skip can exit 0): a .ko under - # //// means success. Otherwise dkms's own - # make.log separates the two failure modes: it exists only when a - # build was attempted (and failed); a BUILD_EXCLUSIVE skip never - # creates it. The log is printed inline because the private dkms - # tree is deleted on exit, so it is the only surviving record in CI. - kos=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/$$BASE" -name '*.ko' 2>/dev/null || true); \ - if [ "$$dkms_rc" -ne 0 ] || [ -z "$$kos" ]; then \ - mklog=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER" -name make.log 2>/dev/null | head -1 || true); \ - if [ -n "$$mklog" ]; then \ - echo "ERROR: dkms build failed for $$PKG_NAME/$$PKG_VER on kernel $$BASE (dkms exit $$dkms_rc); make.log tail:" >&2; \ - tail -n 300 "$$mklog" | sed 's/^/ | /' >&2; \ - else \ - echo "ERROR: $$PKG_NAME/$$PKG_VER produced no module for kernel $$BASE; dkms attempted no build (dkms exit $$dkms_rc)." >&2; \ - gates=$$(grep -E '^[[:space:]]*BUILD_EXCLUSIVE' "$$conf" 2>/dev/null || true); \ - if [ -n "$$gates" ]; then \ - echo " BUILD_EXCLUSIVE gates declared by $$conf:" >&2; \ - printf '%s\n' "$$gates" | sed 's/^[[:space:]]*/ | /' >&2; \ - for c in $$(printf '%s\n' "$$gates" | sed -n 's/^[[:space:]]*BUILD_EXCLUSIVE_CONFIG=//p' | tr -d '"'); do \ - if grep -q "^$$c=[ym]" "$$PKG/boot/config-$$BASE" 2>/dev/null; then \ - echo " | $$c is set in this kernel's config" >&2; \ - else \ - echo " | $$c is NOT set in this kernel's config" >&2; \ - fi; \ - done; \ - echo " This kernel: $$BASE, dkms arch $(DKMS_ARCH)." >&2; \ - else \ - echo " No BUILD_EXCLUSIVE gates are declared in $$conf; see the dkms output above." >&2; \ - fi; \ - comp=$$(find "$$DKMS_TREE/$$PKG_NAME/$$PKG_VER/$$BASE" \( -name '*.ko.gz' -o -name '*.ko.xz' -o -name '*.ko.zst' \) 2>/dev/null | head -1 || true); \ - [ -z "$$comp" ] || echo " Note: found compressed module output ($$comp); compressed dkms output is not supported." >&2; \ - fi; \ - echo "Refusing to ship linux-image-$$BASE-qcom without $$PKG_NAME." >&2; \ - exit 1; \ - fi; \ - # Mirror the in-tree module treatment for each bundled .ko. In-tree, the - # unstripped build-tree copy is the debug donor while modules_install - # writes a separately stripped copy (INSTALL_MOD_STRIP=1, i.e. STRIP - # --strip-debug). We have only the one dkms-built .ko, so both roles are - # performed on it, in order: - # Stage 1 — copy its debug into the -dbg package's parallel debug tree - # (same path layout as the in-tree extraction above); a - # non-destructive read, so the shipped copy is untouched. - # Stage 2 — strip the shipped copy in place with `strip --strip-debug`, - # matching exactly what modules_install INSTALL_MOD_STRIP=1 - # runs on the in-tree modules (the kernel's STRIP --strip-debug; - # plain strip on this native arm64 build). --strip-debug is - # required for kernel modules — a full strip drops the symtab - # and relocations needed to load. Must follow stage 1. (strip, - # not objcopy: objcopy's single-arg in-place form no-op'd here.) - printf '%s\n' "$$kos" | while IFS= read -r ko; do \ - b=$$(basename "$$ko"); \ - dest="debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/extra/$$b"; \ - dbg="$$DBG_PKG/usr/lib/debug/lib/modules/$$BASE/extra/$$b"; \ - [ ! -e "$$dest" ] || { echo "ERROR: duplicate bundled module name $$b (already bundled by an earlier manifest entry)" >&2; exit 1; }; \ - intree=$$(find "debian/linux-image-$$BASE-qcom/lib/modules/$$BASE/kernel" -name "$$b" -print -quit 2>/dev/null || true); \ - [ -z "$$intree" ] || { echo "ERROR: bundled module $$b collides with in-tree $$intree; module precedence on the target would be ambiguous" >&2; exit 1; }; \ - install -D -m 644 "$$ko" "$$dest"; \ - mkdir -p "$${dbg%/*}"; \ - $(OBJCOPY) --only-keep-debug "$$dest" "$$dbg" 2>/dev/null || cp -a "$$dest" "$$dbg"; \ - strip --strip-debug "$$dest"; \ - done; \ - echo "Bundled $$PKG_NAME modules into debian/linux-image-$$BASE-qcom"; \ - done; \ - else \ - echo "No DKMS modules listed in debian/dkms-modules; skipping"; \ - fi + # Delegated to debian/scripts/bundle-dkms-modules.sh. + # The script reads debian/dkms-modules, builds each listed module against the + # staging headers produced earlier in this same dpkg-buildpackage run, and + # bundles the resulting .ko files into the linux-image and -dbg staging trees. + # It can also be invoked directly by a developer — see the script's --help. + $(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" # Skip automatic dh_installmodules - we'll call it manually in override_dh_installdeb override_dh_installmodules: diff --git a/debian/scripts/bundle-dkms-modules.sh b/debian/scripts/bundle-dkms-modules.sh new file mode 100755 index 0000000..375420e --- /dev/null +++ b/debian/scripts/bundle-dkms-modules.sh @@ -0,0 +1,447 @@ +#!/bin/bash +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +set -euo pipefail + +# bundle-dkms-modules.sh — Build and bundle out-of-tree DKMS modules into the +# linux-image staging tree at dpkg-buildpackage time. +# +# This script is the single source of truth for DKMS module integration. +# It is called by debian/rules override_dh_auto_install after the kernel image, +# modules, headers, and debug packages have been staged, and can also be invoked +# directly by a developer who has already staged those trees manually. +# +# What it does (for each module listed in the manifest): +# 1. Resolves the installed -dkms package via dpkg -L (authoritative, no globbing). +# 2. Reads PACKAGE_NAME / PACKAGE_VERSION from the package's dkms.conf. +# 3. Builds the module with `dkms build` against the staged kernel headers, +# using a private --dkmstree (mktemp) to avoid writing to /var/lib/dkms/. +# 4. Judges the outcome by artifact presence, not dkms exit code. +# On failure: prints make.log tail (build failure) or BUILD_EXCLUSIVE gate +# analysis (skip), then hard-fails — a manifest entry is a presence contract. +# 5. For each produced .ko: +# - Collision-checks against already-bundled modules and in-tree modules. +# - Installs to /lib/modules//extra/.ko +# - Extracts debug symbols to /usr/lib/debug/lib/modules//extra/.ko +# via objcopy --only-keep-debug (Stage 1, non-destructive). +# - Strips the shipped copy with `strip --strip-debug` (Stage 2). +# --strip-debug is required for kernel modules: a full strip drops the +# symtab and relocations needed by the module loader. +# +# PREREQUISITES (must be satisfied before calling this script): +# - The kernel image staging tree must exist at --image-pkg-dir with: +# lib/modules//kernel/ (in-tree modules, for collision detection) +# boot/config- (kernel .config, for BUILD_EXCLUSIVE_CONFIG checks) +# - The debug package staging tree must exist at --dbg-pkg-dir. +# - The kernel headers must be fully staged at --headers-dir (absolute path). +# This is the directory containing Makefile, include/, scripts/, arch/, etc. +# It must be an absolute path: dkms invokes make from inside the module +# source directory, so a relative path would resolve to nothing from there. +# - Each module listed in the manifest must have its -dkms package installed +# in the build environment (declared as Build-Depends in debian/control.in). +# +# USAGE (from debian/rules — CI path): +# 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)/debian/linux-image-$BASE-qcom" \ +# --dbg-pkg-dir "$(CURDIR)/debian/linux-image-$BASE-qcom-dbg" \ +# --arch "$(DKMS_ARCH)" \ +# --objcopy "$(OBJCOPY)" \ +# --modules-manifest "$(CURDIR)/debian/dkms-modules" +# +# USAGE (standalone developer path — after manual staging): +# debian/scripts/bundle-dkms-modules.sh \ +# --kver 6.12.0-qcom-next-20260210 \ +# --headers-dir /path/to/kernel-source/debian/linux-headers-6.12.0-qcom-next-20260210-qcom/usr/src/linux-headers-6.12.0-qcom-next-20260210 \ +# --image-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-qcom \ +# --dbg-pkg-dir /path/to/kernel-source/debian/linux-image-6.12.0-qcom-next-20260210-qcom-dbg + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# --------------------------------------------------------------------------- +# Internal: DKMS source root prefix used when resolving dkms.conf paths from +# dpkg -L output. Defaults to /usr/src (production). Can be overridden via +# environment variable _BUNDLE_DKMS_SRC_ROOT for testing purposes only. +# --------------------------------------------------------------------------- +_DKMS_SRC_ROOT="${_BUNDLE_DKMS_SRC_ROOT:-/usr/src}" + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- +KVER="" +HEADERS_DIR="" +IMAGE_PKG_DIR="" +DBG_PKG_DIR="" +# Default manifest: debian/dkms-modules (one level up from debian/scripts/) +MODULES_MANIFEST="${SCRIPT_DIR}/../dkms-modules" +# dkms --arch speaks uname -m vocabulary (aarch64), not kbuild vocabulary (arm64). +# This matters for dkms.conf BUILD_EXCLUSIVE_ARCH gates. +DKMS_ARCH="aarch64" +# objcopy: prefer the aarch64 cross-compiler's objcopy; fall back to host objcopy. +OBJCOPY="$(which aarch64-linux-gnu-objcopy 2>/dev/null || which objcopy 2>/dev/null || echo objcopy)" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' +log_info() { echo -e "${GREEN}[bundle-dkms]${NC} $*"; } +log_warn() { echo -e "${YELLOW}[bundle-dkms]${NC} $*"; } +log_error() { echo -e "${RED}[bundle-dkms]${NC} $*" >&2; } +log_step() { echo -e "${BLUE}[bundle-dkms]${NC} $*"; } + +# --------------------------------------------------------------------------- +# Usage +# --------------------------------------------------------------------------- +usage() { + cat </lib/modules//extra/ + In debian/rules this is: + \$(CURDIR)/debian/linux-image-\$BASE-qcom + --dbg-pkg-dir DIR Path to the debug package staging tree root. + Debug symbols are installed under: + /usr/lib/debug/lib/modules//extra/ + In debian/rules this is: + \$(CURDIR)/debian/linux-image-\$BASE-qcom-dbg + +OPTIONAL: + --modules-manifest FILE Path to the dkms-modules manifest. + Default: debian/dkms-modules (relative to this script's + location, i.e. \$(dirname \$0)/../dkms-modules). + --arch ARCH Architecture token in uname -m vocabulary passed to + dkms --arch and used for BUILD_EXCLUSIVE_ARCH matching. + Default: aarch64 + --objcopy PATH Path to objcopy binary for debug symbol extraction. + Default: aarch64-linux-gnu-objcopy, then objcopy. + -h, --help Show this help and exit. + +PREREQUISITES (developer standalone use): + 1. The -dkms package for each module in the manifest must be installed. + 2. --headers-dir must point to a fully staged kernel headers tree. + 3. --image-pkg-dir must contain lib/modules//kernel/ (in-tree modules) + and boot/config- (kernel .config). + 4. --dbg-pkg-dir must exist (can be empty; subdirs are created as needed). + 5. --headers-dir must be an absolute path. + +MANIFEST FORMAT (debian/dkms-modules): + One module name per line (without the -dkms suffix). + Lines starting with # and blank lines are ignored. + A corresponding Build-Depends entry must exist in debian/control.in. + +EXAMPLES: + # CI path (called from debian/rules): + debian/scripts/bundle-dkms-modules.sh \\ + --kver 6.12.0-qcom-next-20260210 \\ + --headers-dir /build/kernel/debian/linux-headers-6.12.0-qcom-next-20260210-qcom/usr/src/linux-headers-6.12.0-qcom-next-20260210 \\ + --image-pkg-dir /build/kernel/debian/linux-image-6.12.0-qcom-next-20260210-qcom \\ + --dbg-pkg-dir /build/kernel/debian/linux-image-6.12.0-qcom-next-20260210-qcom-dbg + + # Developer standalone path: + debian/scripts/bundle-dkms-modules.sh \\ + --kver 6.12.0-qcom-next-20260210 \\ + --headers-dir /path/to/staged/linux-headers-6.12.0-qcom-next-20260210 \\ + --image-pkg-dir /path/to/staged/linux-image-6.12.0-qcom-next-20260210-qcom \\ + --dbg-pkg-dir /path/to/staged/linux-image-6.12.0-qcom-next-20260210-qcom-dbg \\ + --arch aarch64 + + # With explicit manifest and objcopy: + debian/scripts/bundle-dkms-modules.sh \\ + --kver 6.12.0-qcom-next-20260210 \\ + --headers-dir /path/to/headers \\ + --image-pkg-dir /path/to/image-pkg \\ + --dbg-pkg-dir /path/to/dbg-pkg \\ + --modules-manifest /path/to/debian/dkms-modules \\ + --objcopy aarch64-linux-gnu-objcopy +EOF + exit 0 +} + +# --------------------------------------------------------------------------- +# Argument parsing +# --------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --kver) KVER="$2"; shift 2 ;; + --headers-dir) HEADERS_DIR="$2"; shift 2 ;; + --image-pkg-dir) IMAGE_PKG_DIR="$2"; shift 2 ;; + --dbg-pkg-dir) DBG_PKG_DIR="$2"; shift 2 ;; + --modules-manifest) MODULES_MANIFEST="$2"; shift 2 ;; + --arch) DKMS_ARCH="$2"; shift 2 ;; + --objcopy) OBJCOPY="$2"; shift 2 ;; + -h|--help) usage ;; + *) log_error "Unknown option: $1"; usage ;; + esac +done + +# --------------------------------------------------------------------------- +# Validate required arguments +# --------------------------------------------------------------------------- +_missing=() +[[ -n "$KVER" ]] || _missing+=(--kver) +[[ -n "$HEADERS_DIR" ]] || _missing+=(--headers-dir) +[[ -n "$IMAGE_PKG_DIR" ]] || _missing+=(--image-pkg-dir) +[[ -n "$DBG_PKG_DIR" ]] || _missing+=(--dbg-pkg-dir) +if [[ ${#_missing[@]} -gt 0 ]]; then + log_error "Missing required arguments: ${_missing[*]}" + log_error "Run with --help for usage." + exit 1 +fi + +# --headers-dir must be absolute (dkms invokes make from inside the module +# source directory, so a relative path resolves to nothing from there). +[[ "$HEADERS_DIR" == /* ]] || { + log_error "--headers-dir must be an absolute path (got: $HEADERS_DIR)" + log_error "dkms invokes make from inside the module source directory;" + log_error "a relative path would resolve to nothing from that location." + exit 1 +} + +# Resolve manifest path to absolute (so it works regardless of cwd) +MODULES_MANIFEST="$(cd "$(dirname "$MODULES_MANIFEST")" 2>/dev/null && pwd)/$(basename "$MODULES_MANIFEST")" + +# --------------------------------------------------------------------------- +# Read and parse the manifest +# --------------------------------------------------------------------------- +# Strip comments (# to end of line), blank lines, and CR line endings. +# Result is a space-separated list of module names. +DKMS_MODULES="" +if [[ -f "$MODULES_MANIFEST" ]]; then + DKMS_MODULES="$(sed -e 's/#.*//' -e 's/\r$//' "$MODULES_MANIFEST" \ + | tr -s ' \t\n' ' ' | sed 's/^ //;s/ $//')" +fi + +if [[ -z "$DKMS_MODULES" ]]; then + log_info "No DKMS modules listed in $MODULES_MANIFEST; skipping" + exit 0 +fi + +# --------------------------------------------------------------------------- +# Validate prerequisites +# --------------------------------------------------------------------------- +[[ -f "$MODULES_MANIFEST" ]] || { + log_error "Modules manifest not found: $MODULES_MANIFEST" + exit 1 +} +[[ -d "$HEADERS_DIR" ]] || { + log_error "--headers-dir does not exist: $HEADERS_DIR" + log_error "The kernel headers must be staged before calling this script." + exit 1 +} +[[ -d "$IMAGE_PKG_DIR" ]] || { + log_error "--image-pkg-dir does not exist: $IMAGE_PKG_DIR" + log_error "The linux-image staging tree must exist before calling this script." + exit 1 +} +if [[ ! -d "$DBG_PKG_DIR" ]]; then + log_warn "--dbg-pkg-dir does not exist: $DBG_PKG_DIR (will be created as needed)" +fi + +log_step "DKMS module bundling configuration:" +log_info " kver: $KVER" +log_info " headers-dir: $HEADERS_DIR" +log_info " image-pkg-dir: $IMAGE_PKG_DIR" +log_info " dbg-pkg-dir: $DBG_PKG_DIR" +log_info " modules-manifest: $MODULES_MANIFEST" +log_info " arch: $DKMS_ARCH" +log_info " objcopy: $OBJCOPY" +log_info " modules: $DKMS_MODULES" +echo + +# --------------------------------------------------------------------------- +# Private DKMS tree — redirects artifacts away from /var/lib/dkms/ (root-owned, +# not writable under fakeroot / non-root dpkg-buildpackage). +# Shared across all modules in this run; cleaned up on EXIT. +# --------------------------------------------------------------------------- +DKMS_TREE="$(mktemp -d)" +trap 'rm -rf "$DKMS_TREE"' EXIT + +# --------------------------------------------------------------------------- +# Main loop: build and bundle each listed module +# --------------------------------------------------------------------------- +for name in $DKMS_MODULES; do + + log_step "Processing DKMS module: $name" + + # ── Resolve the module from the package manager ────────────────────────── + # dpkg -L -dkms is authoritative: the -dkms package ships exactly one + # <_DKMS_SRC_ROOT>//dkms.conf (dh_dkms layout), so the source tree is + # found without guessing and a look-alike directory from another package can + # never be picked up. + conf="$(dpkg -L "${name}-dkms" 2>/dev/null \ + | grep -E "^${_DKMS_SRC_ROOT}/[^/]+/dkms\\.conf\$" || true)" + + if [[ -z "$conf" ]]; then + log_error "${name}-dkms is not installed or ships no ${_DKMS_SRC_ROOT}//dkms.conf" + log_error "Is ${name}-dkms declared in Build-Depends in debian/control.in?" + exit 1 + fi + + conf_count="$(printf '%s\n' "$conf" | wc -l)" + if [[ "$conf_count" -ne 1 ]]; then + log_error "${name}-dkms ships multiple dkms.conf files (expected exactly 1):" + printf '%s\n' "$conf" >&2 + exit 1 + fi + + SRC="${conf%/dkms.conf}" + + # ── Read PACKAGE_NAME / PACKAGE_VERSION from dkms.conf ─────────────────── + # dkms.conf is the authority on the name/version tokens that drive the dkms + # tree layout and the build call. Values are read literally, as installed by + # dh_dkms. The `tail -1` handles the (unusual) case of duplicate keys. + PKG_NAME="$(sed -n 's/^[[:space:]]*PACKAGE_NAME=//p' "$conf" | tail -1 | tr -d '"')" + PKG_VER="$(sed -n 's/^[[:space:]]*PACKAGE_VERSION=//p' "$conf" | tail -1 | tr -d '"')" + + if [[ -z "$PKG_NAME" || -z "$PKG_VER" ]]; then + log_error "PACKAGE_NAME or PACKAGE_VERSION not found in $conf" + exit 1 + fi + + # ── Guard against duplicate manifest entries resolving to the same module ─ + if [[ -e "$DKMS_TREE/$PKG_NAME/$PKG_VER" ]]; then + log_error "Duplicate dkms module $PKG_NAME/$PKG_VER" + log_error "Already prepared by an earlier manifest entry — check $MODULES_MANIFEST" + exit 1 + fi + + # ── Set up the private DKMS tree layout ────────────────────────────────── + # dkms expects: ///source -> + mkdir -p "$DKMS_TREE/$PKG_NAME/$PKG_VER" + ln -sf "$SRC" "$DKMS_TREE/$PKG_NAME/$PKG_VER/source" + + log_info "Building DKMS module $PKG_NAME/$PKG_VER for $KVER" + log_info " source: $SRC" + log_info " kernelsourcedir: $HEADERS_DIR" + log_info " dkmstree: $DKMS_TREE" + + # ── Run dkms build ──────────────────────────────────────────────────────── + # Capture exit code separately: dkms exit-code conventions vary across + # versions (a BUILD_EXCLUSIVE skip can exit 0). Outcome is judged by + # artifact presence, not exit code. + dkms_rc=0 + dkms build "$PKG_NAME/$PKG_VER" \ + --kernelsourcedir "$HEADERS_DIR" \ + --dkmstree "$DKMS_TREE" \ + -k "$KVER" \ + --arch "$DKMS_ARCH" \ + || dkms_rc=$? + + # ── Judge outcome by artifacts ──────────────────────────────────────────── + # A .ko under //// means success. + # dkms's make.log separates the two failure modes: + # - make.log present → build was attempted and failed + # - no make.log → dkms attempted no build (BUILD_EXCLUSIVE gate) + # The log is printed inline because the private dkms tree is deleted on + # EXIT, so it is the only surviving record in CI. + kos="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER/$KVER" -name '*.ko' 2>/dev/null || true)" + + if [[ "$dkms_rc" -ne 0 || -z "$kos" ]]; then + mklog="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER" -name make.log 2>/dev/null \ + | head -1 || true)" + if [[ -n "$mklog" ]]; then + log_error "dkms build failed for $PKG_NAME/$PKG_VER on kernel $KVER (dkms exit $dkms_rc); make.log tail:" + tail -n 300 "$mklog" | sed 's/^/ | /' >&2 + else + log_error "$PKG_NAME/$PKG_VER produced no module for kernel $KVER; dkms attempted no build (dkms exit $dkms_rc)." + gates="$(grep -E '^[[:space:]]*BUILD_EXCLUSIVE' "$conf" 2>/dev/null || true)" + if [[ -n "$gates" ]]; then + echo " BUILD_EXCLUSIVE gates declared by $conf:" >&2 + printf '%s\n' "$gates" | sed 's/^[[:space:]]*/ | /' >&2 + # Evaluate each BUILD_EXCLUSIVE_CONFIG against the staged kernel .config + kernel_config="$IMAGE_PKG_DIR/boot/config-$KVER" + while IFS= read -r gate_line; do + c="$(printf '%s\n' "$gate_line" \ + | sed -n 's/^[[:space:]]*BUILD_EXCLUSIVE_CONFIG=//p' \ + | tr -d '"')" + [[ -n "$c" ]] || continue + if grep -q "^${c}=[ym]" "$kernel_config" 2>/dev/null; then + echo " | $c is set in this kernel's config" >&2 + else + echo " | $c is NOT set in this kernel's config" >&2 + fi + done <<< "$gates" + echo " This kernel: $KVER, dkms arch $DKMS_ARCH." >&2 + else + echo " No BUILD_EXCLUSIVE gates are declared in $conf; see the dkms output above." >&2 + fi + # Check for compressed module output (not supported) + comp="$(find "$DKMS_TREE/$PKG_NAME/$PKG_VER/$KVER" \ + \( -name '*.ko.gz' -o -name '*.ko.xz' -o -name '*.ko.zst' \) \ + 2>/dev/null | head -1 || true)" + [[ -z "$comp" ]] || \ + echo " Note: found compressed module output ($comp); compressed dkms output is not supported." >&2 + fi + log_error "Refusing to ship linux-image-$KVER-qcom without $PKG_NAME." + exit 1 + fi + + # ── Install, extract debug, and strip each produced .ko ────────────────── + # Mirror the in-tree module treatment: + # Stage 1 — install the .ko (unstripped at this point) + # Stage 2 — objcopy --only-keep-debug → debug package (non-destructive read) + # Stage 3 — strip --strip-debug on the shipped copy + # (--strip-debug, not full strip: kernel modules need their symtab + # and relocations to be loadable by the module loader) + # Stage 2 must precede Stage 3 (debug extraction before stripping). + # + # Use process substitution (< <(…)) instead of a pipe to avoid running the + # loop body in a subshell, which would prevent `exit 1` from terminating + # the script on collision errors. + while IFS= read -r ko; do + b="$(basename "$ko")" + dest="$IMAGE_PKG_DIR/lib/modules/$KVER/extra/$b" + dbg="$DBG_PKG_DIR/usr/lib/debug/lib/modules/$KVER/extra/$b" + + # Guard: duplicate bundled module name (two manifest entries → same basename) + if [[ -e "$dest" ]]; then + log_error "Duplicate bundled module name: $b" + log_error "Already bundled by an earlier manifest entry — check $MODULES_MANIFEST" + exit 1 + fi + + # Guard: in-tree collision (bundled module shares name with an in-tree module) + intree="$(find "$IMAGE_PKG_DIR/lib/modules/$KVER/kernel" \ + -name "$b" -print -quit 2>/dev/null || true)" + if [[ -n "$intree" ]]; then + log_error "Bundled module $b collides with in-tree module: $intree" + log_error "Module precedence on the target would be ambiguous (depmod search order)." + exit 1 + fi + + # Stage 1: install the .ko (still unstripped at this point) + install -D -m 644 "$ko" "$dest" + + # Stage 2: extract debug symbols before stripping (non-destructive read) + mkdir -p "${dbg%/*}" + "$OBJCOPY" --only-keep-debug "$dest" "$dbg" 2>/dev/null \ + || cp -a "$dest" "$dbg" + + # Stage 3: strip the shipped copy in place + strip --strip-debug "$dest" + + log_info " Installed: $dest (stripped)" + log_info " Debug: $dbg" + + done < <(printf '%s\n' "$kos") + + log_info "Bundled $PKG_NAME modules into $(basename "$IMAGE_PKG_DIR")" + echo + +done + +log_step "DKMS module bundling complete." From 0815aadb9baae047feb19ec478ffc79ad827726d Mon Sep 17 00:00:00 2001 From: Bjordis Collaku Date: Wed, 24 Jun 2026 16:15:48 -0700 Subject: [PATCH 11/13] debian/rules: fix syntax error that broke override_dh_auto_install 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; ;