kernel: Add MLX5 modules image build infrastructure

Add config fragment, build script, and CI integration for building
Mellanox MLX5/InfiniBand kernel modules as a standalone disk image.

The orchestrator script (build-kernel-modules-images.sh) builds the
kernel with extra module config fragments, runs modules_install,
filters modules by subsystem into per-set staging trees, and
packages each into its own disk image using build-modules-volume.sh.

Since these modules are built within the Kata CI using the same
KBUILD_SIGN_PIN, they are signed and loadable on the official
released Kata kernel.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
This commit is contained in:
Fabiano Fidêncio
2026-04-24 14:07:56 +02:00
parent 2273a32d2c
commit 51ffafa0ac
7 changed files with 423 additions and 1 deletions

View File

@@ -49,6 +49,7 @@ jobs:
- kernel
- kernel-debug
- kernel-dragonball-experimental
- kernel-modules-images
- kernel-nvidia-gpu
- nydus
- ovmf
@@ -103,7 +104,7 @@ jobs:
ARTEFACT_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
TARGET_BRANCH: ${{ inputs.target-branch }}
RELEASE: ${{ inputs.stage == 'release' && 'yes' || 'no' }}
KBUILD_SIGN_PIN: ${{ contains(matrix.asset, 'nvidia') && secrets.KBUILD_SIGN_PIN || '' }}
KBUILD_SIGN_PIN: ${{ (contains(matrix.asset, 'nvidia') || matrix.asset == 'kernel-modules-images') && secrets.KBUILD_SIGN_PIN || '' }}
- name: Parse OCI image name and digest
id: parse-oci-segments

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env bash
#
# Copyright (c) 2026 Kata Contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Build kernel module disk images for kata guest VMs.
#
# This script:
# 1. Invokes build-kernel.sh to compile the kernel with extra module
# config fragments (e.g., MLNX, NTFS3).
# 2. Runs modules_install to collect all built modules.
# 3. Filters modules by subsystem into per-set staging directories.
# 4. Packages each set into a disk image using build-modules-volume.sh.
#
# The kernel binary itself is discarded; only the module images are
# the desired output.
set -o errexit
set -o nounset
set -o pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
packaging_scripts_dir="${script_dir}/../scripts"
# shellcheck source=tools/packaging/scripts/lib.sh
source "${packaging_scripts_dir}/lib.sh"
readonly build_kernel="${script_dir}/build-kernel.sh"
readonly build_modules_volume="${script_dir}/build-modules-volume.sh"
usage() {
cat <<EOF
Build kernel module disk images for kata guest VMs.
Usage:
$(basename "$0") [options]
Options:
-a <arch> Target architecture (default: host arch).
-o <dir> Output directory for module images (default: PWD).
-V Enable dm-verity on module images.
-h Display this help.
The script builds the kernel with all module-set config fragments,
then splits the resulting modules into per-set disk images:
- mlx5: Mellanox MLX5 + InfiniBand drivers
EOF
exit "${1:-0}"
}
# Module sets: name -> array of source paths under lib/modules/<ver>/kernel/
# that belong to this set.
declare -A MODULE_SETS
MODULE_SETS[mlx5]="drivers/net/ethernet/mellanox drivers/infiniband"
# Module set config fragments (relative to fragments/modules/)
declare -A MODULE_FRAGMENTS
MODULE_FRAGMENTS[mlx5]="mlx5.conf"
output_dir="${PWD}"
enable_verity="false"
arch_target=""
while getopts "a:o:Vh" opt; do
case "${opt}" in
a) arch_target="${OPTARG}" ;;
o) output_dir="${OPTARG}" ;;
V) enable_verity="true" ;;
h) usage 0 ;;
*) usage 1 ;;
esac
done
[[ -n "${arch_target}" ]] || arch_target="$(uname -m)"
fragments_dir="${script_dir}/configs/fragments/modules"
for name in "${!MODULE_FRAGMENTS[@]}"; do
frag="${fragments_dir}/${MODULE_FRAGMENTS[${name}]}"
[[ -f "${frag}" ]] || die "Config fragment not found: ${frag}"
done
kernel_version=$(get_from_kata_deps ".assets.kernel.version")
kernel_version="${kernel_version#v}"
config_version=$(cat "${script_dir}/kata_config_version")
kernel_path="${PWD}/kata-linux-modules-${kernel_version}-${config_version}"
info "Building kernel ${kernel_version} with module configs"
# Collect all module fragment paths as extra config files for build-kernel.sh
extra_configs=""
for name in "${!MODULE_FRAGMENTS[@]}"; do
extra_configs="${extra_configs} ${fragments_dir}/${MODULE_FRAGMENTS[${name}]}"
done
# Build the kernel with module support + module signing + extra module fragments.
# We use -x (confidential) so that modules.conf and module_signing.conf are
# included, and KBUILD_SIGN_PIN (if set) is used to sign the modules.
# The -s flag skips redundant config checks since our module fragments may
# overlap with configs already set by the confidential build.
"${build_kernel}" \
-a "${arch_target}" \
-v "${kernel_version}" \
-k "${kernel_path}" \
-x \
-s \
-f \
setup
# Append module fragment configs to the generated .config and re-run olddefconfig
arch_kernel=$(case "${arch_target}" in
x86_64) echo "x86_64" ;;
aarch64) echo "arm64" ;;
s390x) echo "s390" ;;
ppc64le|powerpc64) echo "powerpc" ;;
*) echo "${arch_target}" ;;
esac)
arch_frag_dir="${script_dir}/configs/fragments/${arch_kernel}"
config_path="${arch_frag_dir}/.config"
info "Merging module config fragments into kernel config"
for name in "${!MODULE_FRAGMENTS[@]}"; do
frag="${fragments_dir}/${MODULE_FRAGMENTS[${name}]}"
info " Appending: ${frag}"
cat "${frag}" >> "${config_path}"
done
cp "${config_path}" "${kernel_path}/.config"
make -C "${kernel_path}" ARCH="${arch_kernel}" olddefconfig
info "Building kernel"
make -C "${kernel_path}" -j "$(nproc)" ARCH="${arch_kernel}"
info "Installing modules"
make -C "${kernel_path}" -j "$(nproc)" \
INSTALL_MOD_STRIP=1 \
INSTALL_MOD_PATH="${kernel_path}" \
modules_install
# Find the installed modules version directory
modules_base="${kernel_path}/lib/modules"
mod_version=$(ls "${modules_base}" | head -1)
modules_tree="${modules_base}/${mod_version}"
[[ -d "${modules_tree}/kernel" ]] || die "No modules installed at ${modules_tree}/kernel"
info "Modules installed at ${modules_tree}"
mkdir -p "${output_dir}"
for name in "${!MODULE_SETS[@]}"; do
paths="${MODULE_SETS[${name}]}"
staging=$(mktemp -d)
staging_modules="${staging}/lib/modules/${mod_version}"
mkdir -p "${staging_modules}/kernel"
has_modules="false"
for subpath in ${paths}; do
src="${modules_tree}/kernel/${subpath}"
if [[ -d "${src}" ]]; then
dst="${staging_modules}/kernel/${subpath}"
mkdir -p "$(dirname "${dst}")"
cp -a "${src}" "${dst}"
has_modules="true"
fi
done
if [[ "${has_modules}" == "false" ]]; then
info "No modules found for set '${name}', skipping"
rm -rf "${staging}"
continue
fi
# Copy metadata files needed by depmod
for f in modules.order modules.builtin; do
[[ -f "${modules_tree}/${f}" ]] && cp "${modules_tree}/${f}" "${staging_modules}/"
done
# Generate modules.dep and related files for this subset
depmod -b "${staging}" "${mod_version}"
info "Creating intermediate tarball for module set '${name}'"
tarball=$(mktemp --suffix=".tar.gz")
tar -czf "${tarball}" -C "${staging}" .
info "Building disk image for module set '${name}'"
verity_flag=""
[[ "${enable_verity}" == "true" ]] && verity_flag="-V"
# shellcheck disable=SC2086
"${build_modules_volume}" -m "${tarball}" -o "${output_dir}" ${verity_flag}
rm -f "${tarball}"
# Rename generic output to per-set name
mv "${output_dir}/kata-modules-volume.img" "${output_dir}/kata-modules-${name}.img"
if [[ "${enable_verity}" == "true" ]] && [[ -f "${output_dir}/modules_verity_params.txt" ]]; then
mv "${output_dir}/modules_verity_params.txt" "${output_dir}/kata-modules-${name}-verity-params.txt"
fi
info "Module image created: ${output_dir}/kata-modules-${name}.img"
rm -rf "${staging}"
done
info "All module images built successfully"

View File

@@ -588,6 +588,154 @@ build_kernel_headers() {
popd >>/dev/null
}
build_modules_images() {
local kernel_path=${1:-}
[[ -n "${kernel_path}" ]] || die "kernel_path not provided"
[[ -d "${kernel_path}" ]] || die "path to kernel does not exist, use ${script_name} setup"
if [[ "${gpu_vendor}" != "" ]]; then
info "Skipping module images for gpu-vendor kernel (modules are built in-tree)"
return 0
fi
if ! grep -q '^CONFIG_MODULES=y' "${kernel_path}/.config" 2>/dev/null; then
info "Skipping module images: CONFIG_MODULES not enabled for this kernel"
return 0
fi
[[ -n "${arch_target}" ]] || arch_target="$(uname -m)"
arch_target=$(arch_to_kernel "${arch_target}")
local verity_flag=""
if [[ "${measured_rootfs}" == "true" ]]; then
verity_flag="-V"
fi
local modules_frag_dir="${default_config_frags_dir}/modules"
[[ -d "${modules_frag_dir}" ]] || die "No modules fragment directory at ${modules_frag_dir}"
local install_path
install_path=$(readlink -m "${DESTDIR}/${PREFIX}/share/${project_name}")
mkdir -p "${install_path}"
local build_modules_volume="${script_dir}/build-modules-volume.sh"
[[ -x "${build_modules_volume}" ]] || die "build-modules-volume.sh not found at ${build_modules_volume}"
declare -A module_sets
module_sets[mlx5]="drivers/net/ethernet/mellanox drivers/infiniband"
module_sets[ntfs]="fs/ntfs3"
declare -A module_frags
module_frags[mlx5]="mlx5.conf"
module_frags[ntfs]="ntfs.conf"
local frags_to_apply=""
for name in "${!module_frags[@]}"; do
local frag="${modules_frag_dir}/${module_frags[${name}]}"
[[ -f "${frag}" ]] || die "Config fragment not found: ${frag}"
frags_to_apply="${frags_to_apply} ${frag}"
done
pushd "${kernel_path}" >>/dev/null
for frag in ${frags_to_apply}; do
info " Appending: ${frag}"
cat "${frag}" >> .config
done
# shellcheck disable=SC2086
make ARCH="${arch_target}" olddefconfig
info "Rebuilding kernel with module fragment dependencies"
# shellcheck disable=SC2086
make -j "$(nproc)" ARCH="${arch_target}" ${CROSS_BUILD_ARG}
info "Installing modules"
make -j "$(nproc)" INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH="${kernel_path}" modules_install
local modules_base="${kernel_path}/lib/modules"
local mod_version
mod_version=$(ls "${modules_base}" | head -1)
local modules_tree="${modules_base}/${mod_version}"
[[ -d "${modules_tree}/kernel" ]] || die "No modules installed at ${modules_tree}/kernel"
info "Modules installed at ${modules_tree}"
for name in "${!module_sets[@]}"; do
local paths="${module_sets[${name}]}"
local staging
staging=$(mktemp -d)
local staging_modules="${staging}/lib/modules/${mod_version}"
mkdir -p "${staging_modules}/kernel"
local has_modules="false"
for subpath in ${paths}; do
local src="${modules_tree}/kernel/${subpath}"
if [[ -d "${src}" ]]; then
local dst="${staging_modules}/kernel/${subpath}"
mkdir -p "$(dirname "${dst}")"
cp -a "${src}" "${dst}"
has_modules="true"
fi
done
if [[ "${has_modules}" == "false" ]]; then
info "No modules found for set '${name}', skipping"
rm -rf "${staging}"
continue
fi
for f in modules.order modules.builtin; do
[[ -f "${modules_tree}/${f}" ]] && cp "${modules_tree}/${f}" "${staging_modules}/"
done
depmod -b "${staging}" "${mod_version}"
local tarball
tarball=$(mktemp --suffix=".tar.gz")
tar -czf "${tarball}" -C "${staging}" .
# shellcheck disable=SC2086
"${build_modules_volume}" -m "${tarball}" -o "${install_path}" ${verity_flag}
rm -f "${tarball}"
mv "${install_path}/kata-modules-volume.img" "${install_path}/kata-modules-${name}.img"
if [[ -f "${install_path}/modules_verity_params.txt" ]]; then
mv "${install_path}/modules_verity_params.txt" "${install_path}/kata-modules-${name}-verity-params.txt"
info "Verity params: ${install_path}/kata-modules-${name}-verity-params.txt"
fi
info "Module image created: ${install_path}/kata-modules-${name}.img"
rm -rf "${staging}"
done
if [[ "${has_any_modules}" == "true" ]]; then
info "Building combined module image (all module sets)"
for f in modules.order modules.builtin; do
[[ -f "${modules_tree}/${f}" ]] && cp "${modules_tree}/${f}" "${combined_modules}/"
done
depmod -b "${combined_staging}" "${mod_version}"
local combined_tarball
combined_tarball=$(mktemp --suffix=".tar.gz")
tar -czf "${combined_tarball}" -C "${combined_staging}" .
# shellcheck disable=SC2086
"${build_modules_volume}" -m "${combined_tarball}" -o "${install_path}" ${verity_flag}
rm -f "${combined_tarball}"
mv "${install_path}/kata-modules-volume.img" "${install_path}/kata-modules-all.img"
if [[ -f "${install_path}/modules_verity_params.txt" ]]; then
mv "${install_path}/modules_verity_params.txt" "${install_path}/kata-modules-all-verity-params.txt"
info "Verity params: ${install_path}/kata-modules-all-verity-params.txt"
fi
info "Combined module image created: ${install_path}/kata-modules-all.img"
fi
rm -rf "${combined_staging}"
popd >>/dev/null
info "All module images built successfully"
}
install_kata() {
local kernel_path=${1:-}
[[ -n "${kernel_path}" ]] || die "kernel_path not provided"
@@ -795,6 +943,9 @@ main() {
build-headers)
build_kernel_headers "${kernel_path}"
;;
build-modules-images)
build_modules_images "${kernel_path}"
;;
install)
install_kata "${kernel_path}"
;;

View File

@@ -0,0 +1,10 @@
# Mellanox ConnectX networking and InfiniBand drivers (as modules).
# Provides mlx5_core, mlx5_ib, and supporting InfiniBand stack.
CONFIG_NET_VENDOR_MELLANOX=y
CONFIG_MLX5_CORE=m
CONFIG_MLX5_CORE_EN=m
CONFIG_MLX5_EN_ARFS=y
CONFIG_MLX5_EN_RXNFC=y
CONFIG_INFINIBAND=m
CONFIG_INFINIBAND_USER_MAD=m
CONFIG_MLX5_INFINIBAND=m

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
#
# Copyright (c) 2026 Kata Contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# Build kernel module disk images inside the kernel builder container.
set -o errexit
set -o nounset
set -o pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "${script_dir}/../../scripts/lib.sh"
repo_root_dir="${repo_root_dir:-}"
[[ -n "${repo_root_dir}" ]] || die "repo_root_dir is not set"
readonly modules_builder="${repo_root_dir}/tools/packaging/kernel/build-kernel-modules-images.sh"
DESTDIR=${DESTDIR:-${PWD}}
PREFIX=${PREFIX:-/opt/kata}
KBUILD_SIGN_PIN="${KBUILD_SIGN_PIN:-}"
output_dir="${DESTDIR}/${PREFIX}/share/kata-containers"
mkdir -p "${output_dir}"
container_image="${KERNEL_CONTAINER_BUILDER:-$(get_kernel_image_name)}"
container_engine="${CONTAINER_ENGINE:-docker}"
"${container_engine}" pull "${container_image}" || \
"${container_engine}" build \
--build-arg "ARCH=${ARCH:-}" \
-t "${container_image}" \
"${script_dir}/../kernel"
"${container_engine}" run --rm -i -v "${repo_root_dir}:${repo_root_dir}" \
--privileged \
-w "${PWD}" \
--env KBUILD_SIGN_PIN="${KBUILD_SIGN_PIN}" \
"${container_image}" \
bash -c "${modules_builder} -a ${ARCH:-$(uname -m)} -o ${output_dir}"

View File

@@ -30,6 +30,7 @@ RUN apt-get update && \
gettext \
rsync \
cpio \
cryptsetup-bin \
patch \
python3 && \
if [ "${ARCH}" != "$(uname -m)" ]; then apt-get install --no-install-recommends -y gcc-"${ARCH}"-linux-gnu binutils-"${ARCH}"-linux-gnu; fi && \

View File

@@ -82,6 +82,15 @@ container_build+=" --build-arg ARCH=${ARCH:-}"
"${container_image}" \
bash -c "${kernel_builder} ${kernel_builder_args} build"
"${container_engine}" run --rm -i -v "${repo_root_dir}:${repo_root_dir}" \
-w "${PWD}" \
--env DESTDIR="${DESTDIR}" --env PREFIX="${PREFIX}" \
--env KERNEL_DEBUG_ENABLED="${KERNEL_DEBUG_ENABLED}" \
--env KBUILD_SIGN_PIN="${KBUILD_SIGN_PIN}" \
--user "$(id -u)":"$(id -g)" \
"${container_image}" \
bash -c "${kernel_builder} ${kernel_builder_args} build-modules-images"
"${container_engine}" run --rm -i -v "${repo_root_dir}:${repo_root_dir}" \
-w "${PWD}" \
--env DESTDIR="${DESTDIR}" --env PREFIX="${PREFIX}" \