2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-24 16:54:03 +00:00

Merge pull request #7843 from VersatusHPC/fix/ubuntu-netboot-initrd-firmware

fix(xcat-core): grub2 cannot load the Ubuntu 26.04 ppc64el netboot initrd
This commit is contained in:
Daniel Hilst
2026-09-24 01:40:18 -03:00
committed by GitHub
2 changed files with 228 additions and 2 deletions
+80 -2
View File
@@ -1827,8 +1827,13 @@ EOMS
}
}
if (-d "$rootimg_dir/lib/firmware/") {
system("cp -r $rootimg_dir/lib/firmware/* /tmp/xcatinitrd.$$/lib/firmware");
# The whole firmware tree is 666 MB on Ubuntu 26.04, and grub2 on a pseries node cannot
# load an initrd of that size. The initrd only has to reach the network.
foreach my $firmware (
initrd_firmware_files($rootimg_dir, \@filestoadd, $kernelver, $customdir, $pathtofiles))
{
copy_initrd_file("$rootimg_dir/lib/firmware/$firmware",
"/tmp/xcatinitrd.$$/lib/firmware/$firmware");
}
if (-d "$rootimg_dir/lib/modules/$kernelver/") {
@@ -1892,6 +1897,79 @@ sub isnetdriver {
}
}
#-------------------------------------------------------------------------------
=head3 initrd_firmware_files
Descriptions: Select the firmware that the drivers in the netboot initrd ask for.
Arguments:
rootimg_dir - the root image directory
filestoadd - reference to the list of files that go into the initrd. A module
is either a plain path or a [ source, destination ] pair.
kernelver - the kernel release, which names one of the firmware directories
module_dirs - directories searched for a module before the root image, in the
same order the copy step searches them
Returns: the firmware files that exist, as paths below lib/firmware
=cut
#-------------------------------------------------------------------------------
sub initrd_firmware_files {
my ($rootimg_dir, $filestoadd, $kernelver, @module_dirs) = @_;
return () unless (-d "$rootimg_dir/lib/firmware");
my %wanted;
foreach my $entry (@$filestoadd) {
my $module = ref($entry) ? $entry->[0] : $entry;
next unless ($module =~ /\.ko(?:\.(?:gz|xz|zst))?$/);
# Ask about the module the copy step takes, which is the custom one where there is
# one. A custom driver is not in the root image at all.
my $source;
foreach my $dir (grep { defined && length } @module_dirs, $rootimg_dir) {
next unless (-f "$dir/$module");
$source = "$dir/$module";
last;
}
next unless (defined $source);
my $out = `modinfo -F firmware "$source" 2>/dev/null`;
if ($?) {
print "Warning: cannot read the firmware of $module\n";
next;
}
foreach my $name (split(/\n/, $out)) {
$name =~ s/^\s+|\s+$//g;
$wanted{$name} = 1 if (length $name);
}
}
# The kernel looks for a firmware name in these directories below lib/firmware, in this
# order, and loads the first file it finds. Keep every one that exists, so an override
# still wins on the node.
my @dirs = ("updates", "");
if (defined $kernelver and length $kernelver) {
unshift @dirs, "updates/$kernelver";
splice(@dirs, 2, 0, $kernelver);
}
my @files;
foreach my $name (sort keys %wanted) {
foreach my $dir (@dirs) {
my $prefix = length($dir) ? "$dir/" : "";
# Ubuntu keeps the firmware compressed and the kernel asks for the plain name.
foreach my $candidate ($name, "$name.zst", "$name.xz", "$name.gz") {
next unless (-e "$rootimg_dir/lib/firmware/$prefix$candidate");
push @files, "$prefix$candidate";
last;
}
}
}
return @files;
}
sub find_rootimg_file {
my ($file) = @_;
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use Test::More;
# grub2 on a pseries node cannot load the netboot initrd when it carries the whole Ubuntu
# firmware tree. Run genimage's firmware step over a root image that holds firmware no
# driver in the initrd asks for, and read what the step put in the initrd.
my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
my $genimage = File::Spec->catfile(
$repo_root, 'xCAT-server', 'share', 'xcat', 'netboot', 'ubuntu', 'genimage');
die "genimage not found at $genimage\n" unless -f $genimage;
my $src = do { local $/; open my $fh, '<', $genimage or die $!; <$fh> };
# The step sits between the loop that copies the initrd files and the copy of the module
# index. Both anchors hold whatever the firmware step itself looks like.
my ($step) = $src =~ m{
copy_initrd_file\(\$srcpath,\ "/tmp/xcatinitrd\.\$\$/\$_"\);\n
\ {8}\}\n\ {4}\}\n
(.*?)
\n\ {4}if\ \(-d\ "\$rootimg_dir/lib/modules/\$kernelver/"\)\ \{
}sx;
die "genimage no longer has a firmware step between the initrd file loop and the module index\n"
unless defined $step;
my ($copy_initrd_file) = $src =~ m{^(sub copy_initrd_file \{.*?\n\}\n)}ms;
die "genimage no longer defines copy_initrd_file\n" unless defined $copy_initrd_file;
# The helper the step calls once the firmware copy is filtered. A genimage that copies the
# tree whole has no such routine, and the step below then needs none.
my ($helper) = $src =~ m{^(sub initrd_firmware_files \{.*?\n\}\n)}ms;
my $scratch = tempdir(CLEANUP => 1);
my $rootimg = "$scratch/rootimg";
my $initrd_dir = "$scratch/initrd";
my $rewritten = ($step =~ s{/tmp/xcatinitrd\.\$\$}{$initrd_dir}g);
die "the firmware step no longer writes to /tmp/xcatinitrd.\$\$\n" unless $rewritten;
# modinfo reports the firmware of a module. Answer for the modules of this root image only.
my $bin = "$scratch/bin";
make_path($bin);
open(my $fake, '>', "$bin/modinfo") or die $!;
print $fake <<'SH';
#!/bin/sh
case "$*" in
*mlx5_core*) echo mellanox/fw-a.mfa2 ;;
*bnx2x*) echo bnx2x/bnx2x-e2.fw ;;
*e1000e*) echo intel/absent-from-this-image.bin ;;
*custom_nic*) echo custom/custom-nic.bin ;;
*virtio_net*) : ;;
*) exit 1 ;;
esac
SH
close($fake);
chmod 0755, "$bin/modinfo";
$ENV{PATH} = "$bin:$ENV{PATH}";
foreach my $file (
'lib/firmware/mellanox/fw-a.mfa2',
'lib/firmware/updates/7.0.0/mellanox/fw-a.mfa2',
'lib/firmware/custom/custom-nic.bin',
'lib/firmware/bnx2x/bnx2x-e2.fw.zst',
'lib/firmware/amdgpu/never-asked-for.bin',
'lib/firmware/qcom/never-asked-for-either.bin',
'lib/modules/7.0.0/kernel/drivers/net/virtio_net.ko',
'lib/modules/7.0.0/kernel/drivers/net/mlx5_core.ko',
'lib/modules/7.0.0/kernel/drivers/net/bnx2x.ko.zst',
'lib/modules/7.0.0/kernel/drivers/net/e1000e.ko',
'bin/busybox',
)
{
my $full = "$rootimg/$file";
($full =~ m{^(.*)/[^/]+$}) and make_path($1);
open(my $fh, '>', $full) or die $!;
print $fh "content of $file\n";
close($fh);
}
make_path("$initrd_dir/lib/firmware");
# A driver the administrator put in the custom directory. genimage takes the module from there
# and not from the root image, so that is the file its firmware has to be read from.
my $customdir = "$scratch/custom";
my $pathtofiles = "$scratch/pathtofiles";
make_path("$customdir/lib/modules/7.0.0/kernel/drivers/net");
make_path($pathtofiles);
open(my $custom, '>', "$customdir/lib/modules/7.0.0/kernel/drivers/net/custom_nic.ko") or die $!;
print $custom "content of a custom driver\n";
close($custom);
{
package Scratch;
use strict;
use warnings;
use File::Basename;
use File::Copy;
use File::Path qw(mkpath);
our $rootimg_dir;
our $customdir;
our $pathtofiles;
our $kernelver;
our @filestoadd;
sub xdie { die @_ }
}
## no critic (BuiltinFunctions::ProhibitStringyEval)
eval "package Scratch;\n$copy_initrd_file\n1" or die $@;
if (defined $helper) {
eval "package Scratch;\n$helper\n1" or die $@;
}
$Scratch::rootimg_dir = $rootimg;
$Scratch::customdir = $customdir;
$Scratch::pathtofiles = $pathtofiles;
$Scratch::kernelver = '7.0.0';
@Scratch::filestoadd = (
[ 'lib/modules/7.0.0/kernel/drivers/net/virtio_net.ko', 'lib/virtio_net.ko' ],
[ 'lib/modules/7.0.0/kernel/drivers/net/mlx5_core.ko', 'lib/mlx5_core.ko' ],
[ 'lib/modules/7.0.0/kernel/drivers/net/bnx2x.ko.zst', 'lib/bnx2x.ko' ],
[ 'lib/modules/7.0.0/kernel/drivers/net/e1000e.ko', 'lib/e1000e.ko' ],
[ 'lib/modules/7.0.0/kernel/drivers/net/custom_nic.ko', 'lib/custom_nic.ko' ],
[ 'bin/busybox', 'bin/busybox' ],
);
eval "package Scratch;\nno strict 'vars';\n$step\n1" or die $@;
ok(-e "$initrd_dir/lib/firmware/mellanox/fw-a.mfa2",
'the initrd keeps the firmware a driver in it asks for');
ok(-e "$initrd_dir/lib/firmware/bnx2x/bnx2x-e2.fw.zst",
'a compressed firmware file answers the plain name the driver asks for');
ok(!-e "$initrd_dir/lib/firmware/amdgpu/never-asked-for.bin",
'the initrd does not carry firmware no driver in it asks for');
ok(!-e "$initrd_dir/lib/firmware/qcom/never-asked-for-either.bin",
'the whole firmware tree does not reach the initrd');
ok(!-e "$initrd_dir/lib/firmware/intel/absent-from-this-image.bin",
'a firmware name the root image does not have is left out');
ok(-e "$initrd_dir/lib/firmware/custom/custom-nic.bin",
'the firmware of a driver taken from the custom directory reaches the initrd');
ok(-e "$initrd_dir/lib/firmware/updates/7.0.0/mellanox/fw-a.mfa2",
'a firmware override under updates/<kernel> reaches the initrd');
done_testing();