Skip to content

Commit 2204b66

Browse files
bradfitzgopherbot
authored andcommitted
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 <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> Auto-Submit: Brad Fitzpatrick <[email protected]> Reviewed-by: David Chase <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 72f772c commit 2204b66

File tree

4 files changed

+177
-2
lines changed

4 files changed

+177
-2
lines changed

cpu/cpu_linux_arm64.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
package cpu
66

7+
import (
8+
"strings"
9+
"syscall"
10+
)
11+
712
// HWCAP/HWCAP2 bits. These are exposed by Linux.
813
const (
914
hwcap_FP = 1 << 0
@@ -32,10 +37,45 @@ const (
3237
hwcap_ASIMDFHM = 1 << 23
3338
)
3439

40+
// linuxKernelCanEmulateCPUID reports whether we're running
41+
// on Linux 4.11+. Ideally we'd like to ask the question about
42+
// whether the current kernel contains
43+
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=77c97b4ee21290f5f083173d957843b615abbff2
44+
// but the version number will have to do.
45+
func linuxKernelCanEmulateCPUID() bool {
46+
var un syscall.Utsname
47+
syscall.Uname(&un)
48+
var sb strings.Builder
49+
for _, b := range un.Release[:] {
50+
if b == 0 {
51+
break
52+
}
53+
sb.WriteByte(byte(b))
54+
}
55+
major, minor, _, ok := parseRelease(sb.String())
56+
return ok && (major > 4 || major == 4 && minor >= 11)
57+
}
58+
3559
func doinit() {
3660
if err := readHWCAP(); err != nil {
37-
// failed to read /proc/self/auxv, try reading registers directly
38-
readARM64Registers()
61+
// We failed to read /proc/self/auxv. This can happen if the binary has
62+
// been given extra capabilities(7) with /bin/setcap.
63+
//
64+
// When this happens, we have two options. If the Linux kernel is new
65+
// enough (4.11+), we can read the arm64 registers directly which'll
66+
// trap into the kernel and then return back to userspace.
67+
//
68+
// But on older kernels, such as Linux 4.4.180 as used on many Synology
69+
// devices, calling readARM64Registers (specifically getisar0) will
70+
// cause a SIGILL and we'll die. So for older kernels, parse /proc/cpuinfo
71+
// instead.
72+
//
73+
// See golang/go#57336.
74+
if linuxKernelCanEmulateCPUID() {
75+
readARM64Registers()
76+
} else {
77+
readLinuxProcCPUInfo()
78+
}
3979
return
4080
}
4181

cpu/parse.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cpu
6+
7+
import "strconv"
8+
9+
// parseRelease parses a dot-separated version number. It follows the semver
10+
// syntax, but allows the minor and patch versions to be elided.
11+
//
12+
// This is a copy of the Go runtime's parseRelease from
13+
// https://golang.org/cl/209597.
14+
func parseRelease(rel string) (major, minor, patch int, ok bool) {
15+
// Strip anything after a dash or plus.
16+
for i := 0; i < len(rel); i++ {
17+
if rel[i] == '-' || rel[i] == '+' {
18+
rel = rel[:i]
19+
break
20+
}
21+
}
22+
23+
next := func() (int, bool) {
24+
for i := 0; i < len(rel); i++ {
25+
if rel[i] == '.' {
26+
ver, err := strconv.Atoi(rel[:i])
27+
rel = rel[i+1:]
28+
return ver, err == nil
29+
}
30+
}
31+
ver, err := strconv.Atoi(rel)
32+
rel = ""
33+
return ver, err == nil
34+
}
35+
if major, ok = next(); !ok || rel == "" {
36+
return
37+
}
38+
if minor, ok = next(); !ok || rel == "" {
39+
return
40+
}
41+
patch, ok = next()
42+
return
43+
}

cpu/parse_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cpu
6+
7+
import "testing"
8+
9+
type parseReleaseTest struct {
10+
in string
11+
major, minor, patch int
12+
}
13+
14+
var parseReleaseTests = []parseReleaseTest{
15+
{"", -1, -1, -1},
16+
{"x", -1, -1, -1},
17+
{"5", 5, 0, 0},
18+
{"5.12", 5, 12, 0},
19+
{"5.12-x", 5, 12, 0},
20+
{"5.12.1", 5, 12, 1},
21+
{"5.12.1-x", 5, 12, 1},
22+
{"5.12.1.0", 5, 12, 1},
23+
{"5.20496382327982653440", -1, -1, -1},
24+
}
25+
26+
func TestParseRelease(t *testing.T) {
27+
for _, test := range parseReleaseTests {
28+
major, minor, patch, ok := parseRelease(test.in)
29+
if !ok {
30+
major, minor, patch = -1, -1, -1
31+
}
32+
if test.major != major || test.minor != minor || test.patch != patch {
33+
t.Errorf("parseRelease(%q) = (%v, %v, %v) want (%v, %v, %v)",
34+
test.in, major, minor, patch,
35+
test.major, test.minor, test.patch)
36+
}
37+
}
38+
}

cpu/proc_cpuinfo_linux.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build linux && arm64
6+
// +build linux,arm64
7+
8+
package cpu
9+
10+
import (
11+
"errors"
12+
"io"
13+
"os"
14+
"strings"
15+
)
16+
17+
func readLinuxProcCPUInfo() error {
18+
f, err := os.Open("/proc/cpuinfo")
19+
if err != nil {
20+
return err
21+
}
22+
defer f.Close()
23+
24+
var buf [1 << 10]byte // enough for first CPU
25+
n, err := io.ReadFull(f, buf[:])
26+
if err != nil && err != io.ErrUnexpectedEOF {
27+
return err
28+
}
29+
in := string(buf[:n])
30+
const features = "\nFeatures : "
31+
i := strings.Index(in, features)
32+
if i == -1 {
33+
return errors.New("no CPU features found")
34+
}
35+
in = in[i+len(features):]
36+
if i := strings.Index(in, "\n"); i != -1 {
37+
in = in[:i]
38+
}
39+
m := map[string]*bool{}
40+
41+
initOptions() // need it early here; it's harmless to call twice
42+
for _, o := range options {
43+
m[o.Name] = o.Feature
44+
}
45+
// The EVTSTRM field has alias "evstrm" in Go, but Linux calls it "evtstrm".
46+
m["evtstrm"] = &ARM64.HasEVTSTRM
47+
48+
for _, f := range strings.Fields(in) {
49+
if p, ok := m[f]; ok {
50+
*p = true
51+
}
52+
}
53+
return nil
54+
}

0 commit comments

Comments
 (0)