mirror of
https://github.com/rwinkhart/sys.git
synced 2026-08-27 20:36:31 -04:00
cpu: parse /proc/cpuinfo on linux/arm64 on old kernels when needed
Updates tailscale/tailscale#5793 Fixes golang/go#57336 Change-Id: I4f8128bebcc58f265d447ecaaad2473aafa9131c Reviewed-on: https://go-review.googlesource.com/c/sys/+/458315 Reviewed-by: Michael Pratt <mpratt@google.com> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Auto-Submit: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: David Chase <drchase@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
committed by
Gopher Robot
parent
72f772c4d7
commit
2204b6615f
+42
-2
@@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
package cpu
|
package cpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
// HWCAP/HWCAP2 bits. These are exposed by Linux.
|
// HWCAP/HWCAP2 bits. These are exposed by Linux.
|
||||||
const (
|
const (
|
||||||
hwcap_FP = 1 << 0
|
hwcap_FP = 1 << 0
|
||||||
@@ -32,10 +37,45 @@ const (
|
|||||||
hwcap_ASIMDFHM = 1 << 23
|
hwcap_ASIMDFHM = 1 << 23
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// linuxKernelCanEmulateCPUID reports whether we're running
|
||||||
|
// on Linux 4.11+. Ideally we'd like to ask the question about
|
||||||
|
// whether the current kernel contains
|
||||||
|
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=77c97b4ee21290f5f083173d957843b615abbff2
|
||||||
|
// but the version number will have to do.
|
||||||
|
func linuxKernelCanEmulateCPUID() bool {
|
||||||
|
var un syscall.Utsname
|
||||||
|
syscall.Uname(&un)
|
||||||
|
var sb strings.Builder
|
||||||
|
for _, b := range un.Release[:] {
|
||||||
|
if b == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sb.WriteByte(byte(b))
|
||||||
|
}
|
||||||
|
major, minor, _, ok := parseRelease(sb.String())
|
||||||
|
return ok && (major > 4 || major == 4 && minor >= 11)
|
||||||
|
}
|
||||||
|
|
||||||
func doinit() {
|
func doinit() {
|
||||||
if err := readHWCAP(); err != nil {
|
if err := readHWCAP(); err != nil {
|
||||||
// failed to read /proc/self/auxv, try reading registers directly
|
// We failed to read /proc/self/auxv. This can happen if the binary has
|
||||||
readARM64Registers()
|
// been given extra capabilities(7) with /bin/setcap.
|
||||||
|
//
|
||||||
|
// When this happens, we have two options. If the Linux kernel is new
|
||||||
|
// enough (4.11+), we can read the arm64 registers directly which'll
|
||||||
|
// trap into the kernel and then return back to userspace.
|
||||||
|
//
|
||||||
|
// But on older kernels, such as Linux 4.4.180 as used on many Synology
|
||||||
|
// devices, calling readARM64Registers (specifically getisar0) will
|
||||||
|
// cause a SIGILL and we'll die. So for older kernels, parse /proc/cpuinfo
|
||||||
|
// instead.
|
||||||
|
//
|
||||||
|
// See golang/go#57336.
|
||||||
|
if linuxKernelCanEmulateCPUID() {
|
||||||
|
readARM64Registers()
|
||||||
|
} else {
|
||||||
|
readLinuxProcCPUInfo()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// parseRelease parses a dot-separated version number. It follows the semver
|
||||||
|
// syntax, but allows the minor and patch versions to be elided.
|
||||||
|
//
|
||||||
|
// This is a copy of the Go runtime's parseRelease from
|
||||||
|
// https://golang.org/cl/209597.
|
||||||
|
func parseRelease(rel string) (major, minor, patch int, ok bool) {
|
||||||
|
// Strip anything after a dash or plus.
|
||||||
|
for i := 0; i < len(rel); i++ {
|
||||||
|
if rel[i] == '-' || rel[i] == '+' {
|
||||||
|
rel = rel[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next := func() (int, bool) {
|
||||||
|
for i := 0; i < len(rel); i++ {
|
||||||
|
if rel[i] == '.' {
|
||||||
|
ver, err := strconv.Atoi(rel[:i])
|
||||||
|
rel = rel[i+1:]
|
||||||
|
return ver, err == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ver, err := strconv.Atoi(rel)
|
||||||
|
rel = ""
|
||||||
|
return ver, err == nil
|
||||||
|
}
|
||||||
|
if major, ok = next(); !ok || rel == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if minor, ok = next(); !ok || rel == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
patch, ok = next()
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type parseReleaseTest struct {
|
||||||
|
in string
|
||||||
|
major, minor, patch int
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseReleaseTests = []parseReleaseTest{
|
||||||
|
{"", -1, -1, -1},
|
||||||
|
{"x", -1, -1, -1},
|
||||||
|
{"5", 5, 0, 0},
|
||||||
|
{"5.12", 5, 12, 0},
|
||||||
|
{"5.12-x", 5, 12, 0},
|
||||||
|
{"5.12.1", 5, 12, 1},
|
||||||
|
{"5.12.1-x", 5, 12, 1},
|
||||||
|
{"5.12.1.0", 5, 12, 1},
|
||||||
|
{"5.20496382327982653440", -1, -1, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRelease(t *testing.T) {
|
||||||
|
for _, test := range parseReleaseTests {
|
||||||
|
major, minor, patch, ok := parseRelease(test.in)
|
||||||
|
if !ok {
|
||||||
|
major, minor, patch = -1, -1, -1
|
||||||
|
}
|
||||||
|
if test.major != major || test.minor != minor || test.patch != patch {
|
||||||
|
t.Errorf("parseRelease(%q) = (%v, %v, %v) want (%v, %v, %v)",
|
||||||
|
test.in, major, minor, patch,
|
||||||
|
test.major, test.minor, test.patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && arm64
|
||||||
|
// +build linux,arm64
|
||||||
|
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readLinuxProcCPUInfo() error {
|
||||||
|
f, err := os.Open("/proc/cpuinfo")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var buf [1 << 10]byte // enough for first CPU
|
||||||
|
n, err := io.ReadFull(f, buf[:])
|
||||||
|
if err != nil && err != io.ErrUnexpectedEOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
in := string(buf[:n])
|
||||||
|
const features = "\nFeatures : "
|
||||||
|
i := strings.Index(in, features)
|
||||||
|
if i == -1 {
|
||||||
|
return errors.New("no CPU features found")
|
||||||
|
}
|
||||||
|
in = in[i+len(features):]
|
||||||
|
if i := strings.Index(in, "\n"); i != -1 {
|
||||||
|
in = in[:i]
|
||||||
|
}
|
||||||
|
m := map[string]*bool{}
|
||||||
|
|
||||||
|
initOptions() // need it early here; it's harmless to call twice
|
||||||
|
for _, o := range options {
|
||||||
|
m[o.Name] = o.Feature
|
||||||
|
}
|
||||||
|
// The EVTSTRM field has alias "evstrm" in Go, but Linux calls it "evtstrm".
|
||||||
|
m["evtstrm"] = &ARM64.HasEVTSTRM
|
||||||
|
|
||||||
|
for _, f := range strings.Fields(in) {
|
||||||
|
if p, ok := m[f]; ok {
|
||||||
|
*p = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user