Use microcode as CPU firmware

This commit is contained in:
Mikhail Chusavitin
2026-03-15 21:16:17 +03:00
parent ab5a4be7ac
commit a6023372b1
4 changed files with 54 additions and 19 deletions

View File

@@ -8,6 +8,14 @@ import (
"strings" "strings"
) )
var execDmidecode = func(typeNum string) (string, error) {
out, err := exec.Command("dmidecode", "-t", typeNum).Output()
if err != nil {
return "", err
}
return string(out), nil
}
// collectBoard runs dmidecode for types 0, 1, 2 and returns the board record // collectBoard runs dmidecode for types 0, 1, 2 and returns the board record
// plus the BIOS firmware entry. Any failure is logged and returns zero values. // plus the BIOS firmware entry. Any failure is logged and returns zero values.
func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) { func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) {
@@ -141,9 +149,5 @@ func cleanDMIValue(v string) string {
// runDmidecode executes dmidecode -t <typeNum> and returns its stdout. // runDmidecode executes dmidecode -t <typeNum> and returns its stdout.
func runDmidecode(typeNum string) (string, error) { func runDmidecode(typeNum string) (string, error) {
out, err := exec.Command("dmidecode", "-t", typeNum).Output() return execDmidecode(typeNum)
if err != nil {
return "", err
}
return string(out), nil
} }

View File

@@ -24,9 +24,7 @@ func Run(_ runtimeenv.Mode) schema.HardwareIngestRequest {
snap.Board = board snap.Board = board
snap.Firmware = append(snap.Firmware, biosFW...) snap.Firmware = append(snap.Firmware, biosFW...)
cpus, cpuFW := collectCPUs(snap.Board.SerialNumber) snap.CPUs = collectCPUs(snap.Board.SerialNumber)
snap.CPUs = cpus
snap.Firmware = append(snap.Firmware, cpuFW...)
snap.Memory = collectMemory() snap.Memory = collectMemory()
sensorDoc, err := readSensorsJSONDoc() sensorDoc, err := readSensorsJSONDoc()

View File

@@ -6,30 +6,28 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
) )
// collectCPUs runs dmidecode -t 4 and reads microcode version from sysfs. // collectCPUs runs dmidecode -t 4 and enriches CPUs with microcode from sysfs.
func collectCPUs(boardSerial string) ([]schema.HardwareCPU, []schema.HardwareFirmwareRecord) { func collectCPUs(boardSerial string) []schema.HardwareCPU {
out, err := runDmidecode("4") out, err := runDmidecode("4")
if err != nil { if err != nil {
slog.Warn("cpu: dmidecode type 4 failed", "err", err) slog.Warn("cpu: dmidecode type 4 failed", "err", err)
return nil, nil return nil
} }
cpus := parseCPUs(out, boardSerial) cpus := parseCPUs(out, boardSerial)
var firmware []schema.HardwareFirmwareRecord
if mc := readMicrocode(); mc != "" { if mc := readMicrocode(); mc != "" {
firmware = append(firmware, schema.HardwareFirmwareRecord{ for i := range cpus {
DeviceName: "CPU Microcode", cpus[i].Firmware = &mc
Version: mc, }
})
} }
slog.Info("cpu: collected", "count", len(cpus)) slog.Info("cpu: collected", "count", len(cpus))
return cpus, firmware return cpus
} }
// parseCPUs splits dmidecode output into per-processor sections and parses each. // parseCPUs splits dmidecode output into per-processor sections and parses each.
@@ -180,7 +178,7 @@ func parseInt(v string) int {
// readMicrocode reads the CPU microcode revision from sysfs. // readMicrocode reads the CPU microcode revision from sysfs.
// Returns empty string if unavailable. // Returns empty string if unavailable.
func readMicrocode() string { func readMicrocode() string {
data, err := os.ReadFile("/sys/devices/system/cpu/cpu0/microcode/version") data, err := os.ReadFile(filepath.Join(cpuSysBaseDir, "cpu0", "microcode", "version"))
if err != nil { if err != nil {
return "" return ""
} }

View File

@@ -1,6 +1,8 @@
package collector package collector
import ( import (
"os"
"path/filepath"
"testing" "testing"
) )
@@ -63,6 +65,39 @@ func TestParseCPUs_unpopulated_skipped(t *testing.T) {
} }
} }
func TestCollectCPUsSetsFirmwareFromMicrocode(t *testing.T) {
tmp := t.TempDir()
origBase := cpuSysBaseDir
cpuSysBaseDir = tmp
t.Cleanup(func() { cpuSysBaseDir = origBase })
if err := os.MkdirAll(filepath.Join(tmp, "cpu0", "microcode"), 0755); err != nil {
t.Fatalf("mkdir microcode dir: %v", err)
}
if err := os.WriteFile(filepath.Join(tmp, "cpu0", "microcode", "version"), []byte("0x2b000643\n"), 0644); err != nil {
t.Fatalf("write microcode version: %v", err)
}
origRun := execDmidecode
execDmidecode = func(typeNum string) (string, error) {
if typeNum != "4" {
t.Fatalf("unexpected dmidecode type: %s", typeNum)
}
return mustReadFile(t, "testdata/dmidecode_type4.txt"), nil
}
t.Cleanup(func() { execDmidecode = origRun })
cpus := collectCPUs("CAR315KA0803B90")
if len(cpus) != 2 {
t.Fatalf("expected 2 CPUs, got %d", len(cpus))
}
for i, cpu := range cpus {
if cpu.Firmware == nil || *cpu.Firmware != "0x2b000643" {
t.Fatalf("cpu[%d] firmware=%v want microcode", i, cpu.Firmware)
}
}
}
func TestParseCPUStatus(t *testing.T) { func TestParseCPUStatus(t *testing.T) {
tests := []struct { tests := []struct {
input string input string