diff --git a/cpuinfo.go b/cpuinfo.go index 8978cdde..f30c5a44 100644 --- a/cpuinfo.go +++ b/cpuinfo.go @@ -187,6 +187,24 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { return cpuinfo, nil } +func formatSteppingARM(variant, revision string) string { + if variant == "" && revision == "" { + return "" + } + varNum, errV := strconv.ParseUint(variant, 0, 64) + revNum, errR := strconv.ParseUint(revision, 0, 64) + if errV == nil && errR == nil { + return fmt.Sprintf("r%dp%d", varNum, revNum) + } + if errV == nil { + return fmt.Sprintf("r%d", varNum) + } + if errR == nil { + return fmt.Sprintf("p%d", revNum) + } + return fmt.Sprintf("r%sp%s", variant, revision) +} + func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { scanner := bufio.NewScanner(bytes.NewReader(info)) @@ -199,6 +217,8 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { cpuinfo := []CPUInfo{} featuresLine := "" commonCPUInfo := CPUInfo{} + variants := make(map[int]string) + revisions := make(map[int]string) i := 0 if strings.TrimSpace(field[0]) == "Processor" { commonCPUInfo = CPUInfo{ModelName: field[1]} @@ -218,6 +238,9 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { continue } field := strings.SplitN(line, ": ", 2) + if len(field) < 2 { + continue + } switch strings.TrimSpace(field[0]) { case "processor": cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor @@ -241,12 +264,73 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) { case "Features": featuresLine = line case "model name": - cpuinfo[i].ModelName = field[1] + if i == -1 { + commonCPUInfo.ModelName = field[1] + } else { + cpuinfo[i].ModelName = field[1] + } + case "CPU implementer", "cpu implementer": + val := strings.TrimSpace(field[1]) + if i == -1 { + commonCPUInfo.VendorID = val + } else { + cpuinfo[i].VendorID = val + } + case "CPU architecture", "cpu architecture": + val := strings.TrimSpace(field[1]) + if i == -1 { + commonCPUInfo.CPUFamily = val + } else { + cpuinfo[i].CPUFamily = val + } + case "CPU part", "cpu part": + val := strings.TrimSpace(field[1]) + if i == -1 { + commonCPUInfo.Model = val + } else { + cpuinfo[i].Model = val + } + case "CPU variant", "cpu variant": + variants[i] = strings.TrimSpace(field[1]) + case "CPU revision", "cpu revision": + revisions[i] = strings.TrimSpace(field[1]) + } + } + + for j := range cpuinfo { + stepping := formatSteppingARM(variants[j], revisions[j]) + if stepping == "" { + stepping = formatSteppingARM(variants[-1], revisions[-1]) } + cpuinfo[j].Stepping = stepping } - fields := strings.SplitN(featuresLine, ": ", 2) - for i := range cpuinfo { - cpuinfo[i].Flags = strings.Fields(fields[1]) + + if len(cpuinfo) > 0 && cpuinfo[0].VendorID == "" && cpuinfo[len(cpuinfo)-1].VendorID != "" { + last := cpuinfo[len(cpuinfo)-1] + for j := range cpuinfo { + if cpuinfo[j].VendorID == "" { + cpuinfo[j].VendorID = last.VendorID + } + if cpuinfo[j].CPUFamily == "" { + cpuinfo[j].CPUFamily = last.CPUFamily + } + if cpuinfo[j].Model == "" { + cpuinfo[j].Model = last.Model + } + if cpuinfo[j].Stepping == "" { + cpuinfo[j].Stepping = last.Stepping + } + } + } + + if featuresLine != "" { + fields := strings.SplitN(featuresLine, ": ", 2) + if len(fields) > 1 { + flags := strings.Fields(fields[1]) + for i := range cpuinfo { + cpuinfo[i].Flags = flags + } + } } return cpuinfo, nil } diff --git a/cpuinfo_test.go b/cpuinfo_test.go index 9f49eeb3..7a19f06a 100644 --- a/cpuinfo_test.go +++ b/cpuinfo_test.go @@ -94,6 +94,46 @@ CPU revision : 3 Hardware : BCM2835 Revision : c03111 +` + + cpuinfoArm64 = ` +processor : 0 +BogoMIPS : 2000.00 +Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti +CPU implementer : 0x41 +CPU architecture: 8 +CPU variant : 0x0 +CPU part : 0xd4f +CPU revision : 1 + +processor : 1 +BogoMIPS : 2000.00 +Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh rng bti +CPU implementer : 0x41 +CPU architecture: 8 +CPU variant : 0x0 +CPU part : 0xd4f +CPU revision : 1 +` + + cpuinfoArm64Heterogeneous = ` +processor : 0 +BogoMIPS : 2000.00 +Features : fp asimd evtstrm aes +CPU implementer : 0x41 +CPU architecture: 8 +CPU variant : 0x0 +CPU part : 0xd03 +CPU revision : 4 + +processor : 1 +BogoMIPS : 2000.00 +Features : fp asimd evtstrm aes +CPU implementer : 0x48 +CPU architecture: 8 +CPU variant : 0x1 +CPU part : 0xd40 +CPU revision : 2 ` cpuinfoS390x = ` @@ -351,11 +391,32 @@ func TestCPUInfoParseARMLegacy(t *testing.T) { t.Errorf("want number of processors %v, have %v", want, have) } if want, have := "ARMv7 Processor rev 5 (v7l)", cpuinfo[0].ModelName; want != have { + t.Errorf("want model name %v, have %v", want, have) + } + if want, have := "0x41", cpuinfo[0].VendorID; want != have { t.Errorf("want vendor %v, have %v", want, have) } + if want, have := "7", cpuinfo[0].CPUFamily; want != have { + t.Errorf("want family %v, have %v", want, have) + } + if want, have := "0xc07", cpuinfo[0].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r0p5", cpuinfo[0].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } if want, have := "thumb", cpuinfo[1].Flags[2]; want != have { t.Errorf("want flag %v, have %v", want, have) } + if want, have := "0x41", cpuinfo[1].VendorID; want != have { + t.Errorf("want vendor %v, have %v", want, have) + } + if want, have := "0xc07", cpuinfo[1].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r0p5", cpuinfo[1].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } } func TestCPUInfoParseARMLegacyV1(t *testing.T) { @@ -367,8 +428,20 @@ func TestCPUInfoParseARMLegacyV1(t *testing.T) { t.Errorf("want number of processors %v, have %v", want, have) } if want, have := "ARMv6-compatible processor rev 5 (v6l)", cpuinfo[0].ModelName; want != have { + t.Errorf("want model name %v, have %v", want, have) + } + if want, have := "0x41", cpuinfo[0].VendorID; want != have { t.Errorf("want vendor %v, have %v", want, have) } + if want, have := "6TEJ", cpuinfo[0].CPUFamily; want != have { + t.Errorf("want family %v, have %v", want, have) + } + if want, have := "0xb36", cpuinfo[0].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r1p5", cpuinfo[0].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } if want, have := "thumb", cpuinfo[0].Flags[2]; want != have { t.Errorf("want flag %v, have %v", want, have) } @@ -383,13 +456,90 @@ func TestCPUInfoParseARM(t *testing.T) { t.Errorf("want number of processors %v, have %v", want, have) } if want, have := "ARMv7 Processor rev 3 (v7l)", cpuinfo[0].ModelName; want != have { + t.Errorf("want model name %v, have %v", want, have) + } + if want, have := "0x41", cpuinfo[0].VendorID; want != have { t.Errorf("want vendor %v, have %v", want, have) } + if want, have := "7", cpuinfo[0].CPUFamily; want != have { + t.Errorf("want family %v, have %v", want, have) + } + if want, have := "0xd08", cpuinfo[0].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r0p3", cpuinfo[0].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } if want, have := "thumb", cpuinfo[1].Flags[1]; want != have { t.Errorf("want flag %v, have %v", want, have) } } +func TestCPUInfoParseARM64(t *testing.T) { + cpuinfo, err := parseCPUInfoARM([]byte(cpuinfoArm64)) + if err != nil || cpuinfo == nil { + t.Fatalf("unable to parse arm64 cpu info: %v", err) + } + if want, have := 2, len(cpuinfo); want != have { + t.Errorf("want number of processors %v, have %v", want, have) + } + for i := range cpuinfo { + if want, have := "0x41", cpuinfo[i].VendorID; want != have { + t.Errorf("cpu %d: want vendor %v, have %v", i, want, have) + } + if want, have := "8", cpuinfo[i].CPUFamily; want != have { + t.Errorf("cpu %d: want family %v, have %v", i, want, have) + } + if want, have := "0xd4f", cpuinfo[i].Model; want != have { + t.Errorf("cpu %d: want model %v, have %v", i, want, have) + } + if want, have := "r0p1", cpuinfo[i].Stepping; want != have { + t.Errorf("cpu %d: want stepping %v, have %v", i, want, have) + } + if want, have := 2000.00, cpuinfo[i].BogoMips; want != have { + t.Errorf("cpu %d: want bogomips %v, have %v", i, want, have) + } + if want, have := "fp", cpuinfo[i].Flags[0]; want != have { + t.Errorf("cpu %d: want flag %v, have %v", i, want, have) + } + } +} + +func TestCPUInfoParseARM64Heterogeneous(t *testing.T) { + cpuinfo, err := parseCPUInfoARM([]byte(cpuinfoArm64Heterogeneous)) + if err != nil || cpuinfo == nil { + t.Fatalf("unable to parse heterogeneous arm64 cpu info: %v", err) + } + if want, have := 2, len(cpuinfo); want != have { + t.Errorf("want number of processors %v, have %v", want, have) + } + if want, have := "0x41", cpuinfo[0].VendorID; want != have { + t.Errorf("want vendor %v, have %v", want, have) + } + if want, have := "8", cpuinfo[0].CPUFamily; want != have { + t.Errorf("want family %v, have %v", want, have) + } + if want, have := "0xd03", cpuinfo[0].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r0p4", cpuinfo[0].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } + + if want, have := "0x48", cpuinfo[1].VendorID; want != have { + t.Errorf("want vendor %v, have %v", want, have) + } + if want, have := "8", cpuinfo[1].CPUFamily; want != have { + t.Errorf("want family %v, have %v", want, have) + } + if want, have := "0xd40", cpuinfo[1].Model; want != have { + t.Errorf("want model %v, have %v", want, have) + } + if want, have := "r1p2", cpuinfo[1].Stepping; want != have { + t.Errorf("want stepping %v, have %v", want, have) + } +} + func TestCPUInfoParseS390X(t *testing.T) { cpuinfo, err := parseCPUInfoS390X([]byte(cpuinfoS390x)) if err != nil || cpuinfo == nil { @@ -478,3 +628,73 @@ func TestCPUInfoParseLoong64(t *testing.T) { t.Errorf("want CPUFamily '%v', have '%v'", want, have) } } + +func TestFormatSteppingARM(t *testing.T) { + tests := []struct { + variant string + revision string + expected string + }{ + {"", "", ""}, + {"0x0", "1", "r0p1"}, + {"0x1", "5", "r1p5"}, + {"0x0", "3", "r0p3"}, + {"0x2", "0", "r2p0"}, + {"0", "1", "r0p1"}, + {"1", "2", "r1p2"}, + {"0x0", "", "r0"}, + {"", "1", "p1"}, + {"abc", "def", "rabcpdef"}, + } + + for _, tt := range tests { + got := formatSteppingARM(tt.variant, tt.revision) + if got != tt.expected { + t.Errorf("formatSteppingARM(%q, %q) = %q, want %q", tt.variant, tt.revision, got, tt.expected) + } + } +} + +func TestCPUInfoParseARMEdgeCases(t *testing.T) { + input := ` +processor : 0 +cpu implementer : 0x41 +cpu architecture: 8 +cpu variant : 0x0 +cpu part : 0xd03 + +processor : 1 +cpu implementer : 0x41 +cpu architecture: 8 +cpu revision : 2 +cpu part : 0xd03 +` + cpuinfo, err := parseCPUInfoARM([]byte(input)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(cpuinfo) != 2 { + t.Fatalf("expected 2 cpus, got %d", len(cpuinfo)) + } + if cpuinfo[0].VendorID != "0x41" { + t.Errorf("expected vendor 0x41, got %q", cpuinfo[0].VendorID) + } + if cpuinfo[0].CPUFamily != "8" { + t.Errorf("expected family 8, got %q", cpuinfo[0].CPUFamily) + } + if cpuinfo[0].Model != "0xd03" { + t.Errorf("expected model 0xd03, got %q", cpuinfo[0].Model) + } + if cpuinfo[0].Stepping != "r0" { + t.Errorf("expected stepping r0, got %q", cpuinfo[0].Stepping) + } + + if cpuinfo[1].Stepping != "p2" { + t.Errorf("expected stepping p2, got %q", cpuinfo[1].Stepping) + } + + _, err = parseCPUInfoARM([]byte("invalid line\n")) + if err == nil { + t.Errorf("expected error for malformed line, got nil") + } +}