fix: build CPU ucode correctly for early loader
Closes #7729 This follows the steps described in https://www.kernel.org/doc/html/v6.1/x86/microcode.html#early-load-microcode Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
c5bd0ac5cf
commit
e3b4940588
@ -15,6 +15,20 @@ import (
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
)
|
||||
|
||||
// List of globs and destinations for early CPU ucode.
|
||||
//
|
||||
// See https://www.kernel.org/doc/html/v6.1/x86/microcode.html#early-load-microcode.
|
||||
//
|
||||
// We need to repackage the ucode blobs matching the glob into the destination concatenating
|
||||
// them all together.
|
||||
// The resulting blobs should be placed into uncompressed cpio archive prepended to the normal (compressed) initramfs.
|
||||
var earlyCPUUcode = []struct {
|
||||
glob, dst string
|
||||
}{
|
||||
{"/lib/firmware/intel-ucode/*", "kernel/x86/microcode/GenuineIntel.bin"},
|
||||
{"/lib/firmware/amd-ucode/microcode_amd*.bin", "kernel/x86/microcode/AuthenticAMD.bin"},
|
||||
}
|
||||
|
||||
// List of paths to be moved to the future initramfs.
|
||||
var initramfsPaths = []string{
|
||||
constants.FirmwarePath,
|
||||
@ -23,7 +37,12 @@ var initramfsPaths = []string{
|
||||
// Compress builds the squashfs image in the specified destination folder.
|
||||
//
|
||||
// Components which should be placed to the initramfs are moved to the initramfsPath.
|
||||
// Ucode components are moved into a separate designated location.
|
||||
func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error) {
|
||||
if err := ext.handleUcode(initramfsPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, path := range initramfsPaths {
|
||||
if _, err := os.Stat(filepath.Join(ext.rootfsPath, path)); err == nil {
|
||||
if err = moveFiles(filepath.Join(ext.rootfsPath, path), filepath.Join(initramfsPath, path)); err != nil {
|
||||
@ -40,6 +59,62 @@ func (ext *Extension) Compress(squashPath, initramfsPath string) (string, error)
|
||||
return squashPath, cmd.Run()
|
||||
}
|
||||
|
||||
func appendBlob(dst io.Writer, srcPath string) error {
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer src.Close() //nolint:errcheck
|
||||
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = src.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(srcPath)
|
||||
}
|
||||
|
||||
func (ext *Extension) handleUcode(initramfsPath string) error {
|
||||
for _, ucode := range earlyCPUUcode {
|
||||
matches, err := filepath.Glob(filepath.Join(ext.rootfsPath, ucode.glob))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// if some ucode is found, append it to the blob in the initramfs
|
||||
if err = os.MkdirAll(filepath.Dir(filepath.Join(initramfsPath, ucode.dst)), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst, err := os.OpenFile(filepath.Join(initramfsPath, ucode.dst), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dst.Close() //nolint:errcheck
|
||||
|
||||
for _, match := range matches {
|
||||
if err = appendBlob(dst, match); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = dst.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveFiles(srcPath, dstPath string) error {
|
||||
return handleFilesOp(srcPath, dstPath, os.Remove)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ func (ext *Extension) KernelModuleDirectory() string {
|
||||
// GenerateKernelModuleDependencyTreeExtension generates a kernel module dependency tree extension.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules []string, initramfsPath string, printFunc func(format string, v ...any)) (*Extension, error) {
|
||||
func GenerateKernelModuleDependencyTreeExtension(extensionPathsWithKernelModules []string, initramfsPath, extensionTreePath string, printFunc func(format string, v ...any)) (*Extension, error) {
|
||||
printFunc("preparing to run depmod to generate kernel modules dependency tree")
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "ext-modules")
|
||||
@ -93,7 +93,7 @@ func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules
|
||||
kernelVersionPath := contents[0].Name()
|
||||
|
||||
// copy to the same location modules from all extensions
|
||||
for _, path := range extensionsPathWithKernelModules {
|
||||
for _, path := range extensionPathsWithKernelModules {
|
||||
if err = copyFiles(filepath.Join(path, kernelVersionPath), filepath.Join(rootfsKernelModulesPath, kernelVersionPath)); err != nil {
|
||||
return nil, fmt.Errorf("copying kernel modules from %s failed: %w", path, err)
|
||||
}
|
||||
@ -105,14 +105,11 @@ func GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules
|
||||
return nil, fmt.Errorf("error running depmod: %w", err)
|
||||
}
|
||||
|
||||
// we want this temp directory to be present until the extension is compressed later on, so not removing it here
|
||||
kernelModulesDependencyTreeStagingDir, err := os.MkdirTemp("", "module-deps")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we want the extension to survive this function, so not storing in a temporary directory
|
||||
kernelModulesDependencyTreeStagingDir := filepath.Join(extensionTreePath, "modules.dep")
|
||||
|
||||
// we want to make sure the root directory has the right permissions.
|
||||
if err := os.Chmod(kernelModulesDependencyTreeStagingDir, 0o755); err != nil {
|
||||
if err := os.MkdirAll(kernelModulesDependencyTreeStagingDir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/siderolabs/talos/internal/pkg/extensions"
|
||||
)
|
||||
@ -26,19 +27,28 @@ func findExtensionsWithKernelModules(extensions []*extensions.Extension) []strin
|
||||
return modulesPath
|
||||
}
|
||||
|
||||
func buildContents(path string) (io.Reader, error) {
|
||||
var listing bytes.Buffer
|
||||
// buildInitramfsContents builds a list of files to be included into initramfs directly, bypassing extensions squashfs.
|
||||
//
|
||||
// Two listings are returned:
|
||||
// - uncompressedListing is a list of files that should be included into initramfs uncompressed prepended as a first section
|
||||
// - compressedListing is a list of files that should be included into initramfs compressed.
|
||||
func buildInitramfsContents(path string) (compressedListing, uncompressedListing []byte, err error) {
|
||||
var compressedBuffer, uncompressedBuffer bytes.Buffer
|
||||
|
||||
if err := buildContentsRecursive(path, "", &listing); err != nil {
|
||||
return nil, err
|
||||
if err := buildInitramfsContentsRecursive(path, "", &compressedBuffer, &uncompressedBuffer); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &listing, nil
|
||||
return compressedBuffer.Bytes(), uncompressedBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func buildContentsRecursive(basePath, path string, w io.Writer) error {
|
||||
func buildInitramfsContentsRecursive(basePath, path string, compressedW, uncompressedW io.Writer) error {
|
||||
if path != "" {
|
||||
fmt.Fprintf(w, "%s\n", path)
|
||||
if path == "kernel" || strings.HasPrefix(path, "kernel/") {
|
||||
fmt.Fprintf(uncompressedW, "%s\n", path)
|
||||
} else {
|
||||
fmt.Fprintf(compressedW, "%s\n", path)
|
||||
}
|
||||
}
|
||||
|
||||
st, err := os.Stat(filepath.Join(basePath, path))
|
||||
@ -56,7 +66,7 @@ func buildContentsRecursive(basePath, path string, w io.Writer) error {
|
||||
}
|
||||
|
||||
for _, item := range contents {
|
||||
if err = buildContentsRecursive(basePath, filepath.Join(path, item.Name()), w); err != nil {
|
||||
if err = buildInitramfsContentsRecursive(basePath, filepath.Join(path, item.Name()), compressedW, uncompressedW); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type Builder struct {
|
||||
InitramfsPath string
|
||||
// Architecture of the initramfs.
|
||||
Arch string
|
||||
// ExtensionTreePath is a path to the extracted exension tree.
|
||||
// ExtensionTreePath is a path to the extracted extension tree.
|
||||
ExtensionTreePath string
|
||||
// Printf is used for logging.
|
||||
Printf func(format string, v ...any)
|
||||
@ -46,10 +46,10 @@ func (builder *Builder) Build() error {
|
||||
return err
|
||||
}
|
||||
|
||||
extensionsPathWithKernelModules := findExtensionsWithKernelModules(extensionsList)
|
||||
extensionPathsWithKernelModules := findExtensionsWithKernelModules(extensionsList)
|
||||
|
||||
if len(extensionsPathWithKernelModules) > 0 {
|
||||
kernelModuleDepExtension, genErr := extensions.GenerateKernelModuleDependencyTreeExtension(extensionsPathWithKernelModules, builder.InitramfsPath, builder.Printf)
|
||||
if len(extensionPathsWithKernelModules) > 0 {
|
||||
kernelModuleDepExtension, genErr := extensions.GenerateKernelModuleDependencyTreeExtension(extensionPathsWithKernelModules, builder.InitramfsPath, builder.ExtensionTreePath, builder.Printf)
|
||||
if genErr != nil {
|
||||
return genErr
|
||||
}
|
||||
|
@ -5,21 +5,43 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// rebuildInitramfs rebuilds finalized initramfs with extensions.
|
||||
//
|
||||
// If uncompressedListing is not empty, contents will be prepended to the initramfs uncompressed.
|
||||
// Contents from compressedListing will be appended to the initramfs compressed (xz) as a second block.
|
||||
// Original initramfs.xz contents will stay without changes.
|
||||
func (builder *Builder) rebuildInitramfs(tempDir string) error {
|
||||
compressedListing, uncompressedListing, err := buildInitramfsContents(tempDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(uncompressedListing) > 0 {
|
||||
if err = builder.prependUncompressedInitramfs(tempDir, uncompressedListing); err != nil {
|
||||
return fmt.Errorf("error prepending uncompressed initramfs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = builder.appendCompressedInitramfs(tempDir, compressedListing); err != nil {
|
||||
return fmt.Errorf("error appending compressed initramfs: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (builder *Builder) appendCompressedInitramfs(tempDir string, compressedListing []byte) error {
|
||||
builder.Printf("creating system extensions initramfs archive and compressing it")
|
||||
|
||||
// the code below runs the equivalent of:
|
||||
// find $tempDir -print | cpio -H newc --create --reproducible | xz -v -C crc32 -0 -e -T 0 -z
|
||||
|
||||
listing, err := buildContents(tempDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pipeR, pipeW, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -29,9 +51,9 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error {
|
||||
defer pipeW.Close() //nolint:errcheck
|
||||
|
||||
// build cpio image which contains .sqsh images and extensions.yaml
|
||||
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet")
|
||||
cmd1 := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet", "-R", "+0:+0")
|
||||
cmd1.Dir = tempDir
|
||||
cmd1.Stdin = listing
|
||||
cmd1.Stdin = bytes.NewReader(compressedListing)
|
||||
cmd1.Stdout = pipeW
|
||||
cmd1.Stderr = os.Stderr
|
||||
|
||||
@ -83,3 +105,57 @@ func (builder *Builder) rebuildInitramfs(tempDir string) error {
|
||||
|
||||
return destination.Sync()
|
||||
}
|
||||
|
||||
func (builder *Builder) prependUncompressedInitramfs(tempDir string, uncompressedListing []byte) error {
|
||||
builder.Printf("creating uncompressed initramfs archive")
|
||||
|
||||
// the code below runs the equivalent of:
|
||||
// mv initramfs.xz initramfs.xz-old
|
||||
// find $tempDir -print | cpio -H newc --create --reproducible > initramfs.xz
|
||||
// cat initramfs.xz-old >> initramfs.xz
|
||||
// rm initramfs.xz-old
|
||||
|
||||
initramfsOld := builder.InitramfsPath + "-old"
|
||||
|
||||
if err := os.Rename(builder.InitramfsPath, initramfsOld); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destination, err := os.OpenFile(builder.InitramfsPath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer destination.Close() //nolint:errcheck
|
||||
|
||||
cmd := exec.Command("cpio", "-H", "newc", "--create", "--reproducible", "--quiet", "-R", "+0:+0")
|
||||
cmd.Dir = tempDir
|
||||
cmd.Stdin = bytes.NewReader(uncompressedListing)
|
||||
cmd.Stdout = destination
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old, err := os.Open(initramfsOld)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer old.Close() //nolint:errcheck
|
||||
|
||||
if _, err = io.Copy(destination, old); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = destination.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = old.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(initramfsOld)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user