diff --git a/platforms/cpuinfo.go b/platforms/cpuinfo.go new file mode 100644 index 000000000..b7c23cc19 --- /dev/null +++ b/platforms/cpuinfo.go @@ -0,0 +1,85 @@ +package platforms + +import ( + "bufio" + "os" + "runtime" + "strings" + + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/log" + "github.com/pkg/errors" +) + +// Present the ARM instruction set architecture, eg: v7, v8 +var cpuVariant string + +func init() { + if isArmArch(runtime.GOARCH) { + cpuVariant = getCPUVariant() + } else { + cpuVariant = "" + } +} + +// For Linux, the kernel has already detected the ABI, ISA and Features. +// So we don't need to access the ARM registers to detect platform information +// by ourselves. We can just parse these information from /proc/cpuinfo +func getCPUInfo(pattern string) (info string, err error) { + if !isLinuxOS(runtime.GOOS) { + return "", errors.Wrapf(errdefs.ErrNotImplemented, "getCPUInfo for OS %s", runtime.GOOS) + } + + cpuinfo, err := os.Open("/proc/cpuinfo") + if err != nil { + return "", err + } + defer cpuinfo.Close() + + // Start to Parse the Cpuinfo line by line. For SMP SoC, we parse + // the first core is enough. + scanner := bufio.NewScanner(cpuinfo) + for scanner.Scan() { + newline := scanner.Text() + list := strings.Split(newline, ":") + + if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) { + return strings.TrimSpace(list[1]), nil + } + } + + // Check whether the scanner encountered errors + err = scanner.Err() + if err != nil { + return "", err + } + + return "", errors.Wrapf(errdefs.ErrNotFound, "getCPUInfo for pattern: %s", pattern) +} + +func getCPUVariant() string { + variant, err := getCPUInfo("Cpu architecture") + if err != nil { + log.L.WithError(err).Error("failure getting variant") + return "" + } + + switch variant { + case "8": + variant = "v8" + case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)": + variant = "v7" + case "6", "6TEJ": + variant = "v6" + case "5", "5T", "5TE", "5TEJ": + variant = "v5" + case "4", "4T": + variant = "v4" + case "3": + variant = "v3" + default: + variant = "unknown" + } + + return variant +} diff --git a/platforms/cpuinfo_test.go b/platforms/cpuinfo_test.go new file mode 100644 index 000000000..95911efe0 --- /dev/null +++ b/platforms/cpuinfo_test.go @@ -0,0 +1,24 @@ +package platforms + +import ( + "runtime" + "testing" +) + +func TestCPUVariant(t *testing.T) { + if !isArmArch(runtime.GOARCH) || !isLinuxOS(runtime.GOOS) { + t.Skip("only relevant on linux/arm") + } + + variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"} + + p := getCPUVariant() + for _, variant := range variants { + if p == variant { + t.Logf("got valid variant as expected: %#v = %#v\n", p, variant) + return + } + } + + t.Fatalf("could not get valid variant as expected: %v\n", variants) +} diff --git a/platforms/database.go b/platforms/database.go index bd66e2517..362b005a6 100644 --- a/platforms/database.go +++ b/platforms/database.go @@ -5,6 +5,13 @@ import ( "strings" ) +// isLinuxOS returns true if the operating system is Linux. +// +// The OS value should be normalized before calling this function. +func isLinuxOS(os string) bool { + return os == "linux" +} + // These function are generated from from https://golang.org/src/go/build/syslist.go. // // We use switch statements because they are slightly faster than map lookups @@ -21,6 +28,17 @@ func isKnownOS(os string) bool { return false } +// isArmArch returns true if the architecture is ARM. +// +// The arch value should be normalized before being passed to this function. +func isArmArch(arch string) bool { + switch arch { + case "arm", "arm64": + return true + } + return false +} + // isKnownArch returns true if we know about the architecture. // // The arch value should be normalized before being passed to this function. diff --git a/platforms/defaults.go b/platforms/defaults.go index 2b57b4979..ed493ab2c 100644 --- a/platforms/defaults.go +++ b/platforms/defaults.go @@ -16,6 +16,7 @@ func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, - // TODO(stevvooe): Need to resolve GOARM for arm hosts. + // The Variant field will be empty if arch != ARM. + Variant: cpuVariant, } } diff --git a/platforms/defaults_test.go b/platforms/defaults_test.go index 39f2e485d..db8e88ff3 100644 --- a/platforms/defaults_test.go +++ b/platforms/defaults_test.go @@ -12,6 +12,7 @@ func TestDefault(t *testing.T) { expected := specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, + Variant: cpuVariant, } p := DefaultSpec() if !reflect.DeepEqual(p, expected) {