1 // Copyright 2019 The gVisor Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 15 // Package cpuid provides basic functionality for creating and adjusting CPU
16 // feature sets.
17 //
18 // Each architecture should define its own FeatureSet type, that must be
19 // savable, along with an allFeatures map, appropriate arch hooks and a
20 // HostFeatureSet function. This file contains common functionality to all
21 // architectures, which is essentially string munging and some errors.
22 //
23 // Individual architectures may export methods on FeatureSet that are relevant,
24 // e.g. FeatureSet.Vendor(). Common to all architectures, FeatureSets include
25 // HasFeature, which provides a trivial mechanism to test for the presence of
26 // specific hardware features. The hardware features are also defined on a
27 // per-architecture basis.
28 package cpuid
29 30 import (
31 "encoding/binary"
32 "fmt"
33 "os"
34 "runtime"
35 "strings"
36 37 "gvisor.dev/gvisor/pkg/log"
38 "gvisor.dev/gvisor/pkg/sync"
39 )
40 41 // contextID is the package for anyContext.Context.Value keys.
42 type contextID int
43 44 const (
45 // CtxFeatureSet is the FeatureSet for the context.
46 CtxFeatureSet contextID = iota
47 48 // hardware capability bit vector.
49 _AT_HWCAP = 16
50 // hardware capability bit vector 2.
51 _AT_HWCAP2 = 26
52 )
53 54 // anyContext represents context.Context.
55 type anyContext interface {
56 Value(key any) any
57 }
58 59 // FromContext returns the FeatureSet from the context, if available.
60 func FromContext(ctx anyContext) FeatureSet {
61 v := ctx.Value(CtxFeatureSet)
62 if v == nil {
63 return FeatureSet{} // Panics if used.
64 }
65 return v.(FeatureSet)
66 }
67 68 // Feature is a unique identifier for a particular cpu feature. We just use an
69 // int as a feature number on x86 and arm64.
70 //
71 // On x86, features are numbered according to "blocks". Each block is 32 bits, and
72 // feature bits from the same source (cpuid leaf/level) are in the same block.
73 //
74 // On arm64, features are numbered according to the ELF HWCAP definition, from
75 // arch/arm64/include/uapi/asm/hwcap.h.
76 type Feature int
77 78 // allFeatureInfo is the value for allFeatures.
79 type allFeatureInfo struct {
80 // displayName is the short display name for the feature.
81 displayName string
82 83 // shouldAppear indicates whether the feature normally appears in
84 // cpuinfo. This affects FlagString only.
85 shouldAppear bool
86 }
87 88 // String implements fmt.Stringer.String.
89 func (f Feature) String() string {
90 info, ok := allFeatures[f]
91 if ok {
92 return info.displayName
93 }
94 return fmt.Sprintf("[0x%x?]", int(f)) // No given name.
95 }
96 97 // reverseMap is a map from displayName to Feature.
98 var reverseMap = func() map[string]Feature {
99 m := make(map[string]Feature)
100 for feature, info := range allFeatures {
101 if info.displayName != "" {
102 // Sanity check that the name is unique.
103 if old, ok := m[info.displayName]; ok {
104 panic(fmt.Sprintf("feature %v has conflicting values (0x%x vs 0x%x)", info.displayName, old, feature))
105 }
106 m[info.displayName] = feature
107 }
108 }
109 return m
110 }()
111 112 // FeatureFromString returns the Feature associated with the given feature
113 // string plus a bool to indicate if it could find the feature.
114 func FeatureFromString(s string) (Feature, bool) {
115 feature, ok := reverseMap[s]
116 return feature, ok
117 }
118 119 // AllFeatures returns the full set of all possible features.
120 func AllFeatures() (features []Feature) {
121 archFlagOrder(func(f Feature) {
122 features = append(features, f)
123 })
124 return
125 }
126 127 // Subtract returns the features present in fs that are not present in other.
128 // If all features in fs are present in other, Subtract returns nil.
129 //
130 // This does not check for any kinds of incompatibility.
131 func (fs FeatureSet) Subtract(other FeatureSet) (left map[Feature]struct{}) {
132 for feature := range allFeatures {
133 thisHas := fs.HasFeature(feature)
134 otherHas := other.HasFeature(feature)
135 if thisHas && !otherHas {
136 if left == nil {
137 left = make(map[Feature]struct{})
138 }
139 left[feature] = struct{}{}
140 }
141 }
142 return
143 }
144 145 // FlagString prints out supported CPU flags.
146 func (fs FeatureSet) FlagString() string {
147 var s []string
148 archFlagOrder(func(feature Feature) {
149 if !fs.HasFeature(feature) {
150 return
151 }
152 info := allFeatures[feature]
153 if !info.shouldAppear {
154 return
155 }
156 s = append(s, info.displayName)
157 })
158 return strings.Join(s, " ")
159 }
160 161 // ErrIncompatible is returned for incompatible feature sets.
162 type ErrIncompatible struct {
163 reason string
164 }
165 166 // Error implements error.Error.
167 func (e *ErrIncompatible) Error() string {
168 return fmt.Sprintf("incompatible FeatureSet: %v", e.reason)
169 }
170 171 // CheckHostCompatible returns nil if fs is a subset of the host feature set.
172 func (fs FeatureSet) CheckHostCompatible() error {
173 hfs := HostFeatureSet()
174 175 // Check that hfs is a superset of fs.
176 if diff := fs.Subtract(hfs); len(diff) > 0 {
177 return &ErrIncompatible{
178 reason: fmt.Sprintf("missing features: %v", diff),
179 }
180 }
181 182 // Make arch-specific checks.
183 return fs.archCheckHostCompatible(hfs)
184 }
185 186 // +stateify savable
187 type hwCap struct {
188 // hwCap1 stores HWCAP bits exposed through the elf auxiliary vector.
189 hwCap1 uint64
190 // hwCap2 stores HWCAP2 bits exposed through the elf auxiliary vector.
191 hwCap2 uint64
192 }
193 194 // The auxiliary vector of a process on the Linux system can be read
195 // from /proc/self/auxv, and tags and values are stored as 8-bytes
196 // decimal key-value pairs on the 64-bit system.
197 //
198 // $ od -t d8 /proc/self/auxv
199 //
200 // 0000000 33 140734615224320
201 // 0000020 16 3219913727
202 // 0000040 6 4096
203 // 0000060 17 100
204 // 0000100 3 94665627353152
205 // 0000120 4 56
206 // 0000140 5 9
207 // 0000160 7 140425502162944
208 // 0000200 8 0
209 // 0000220 9 94665627365760
210 // 0000240 11 1000
211 // 0000260 12 1000
212 // 0000300 13 1000
213 // 0000320 14 1000
214 // 0000340 23 0
215 // 0000360 25 140734614619513
216 // 0000400 26 0
217 // 0000420 31 140734614626284
218 // 0000440 15 140734614619529
219 // 0000460 0 0
220 func readHWCap(auxvFilepath string) (hwCap, error) {
221 c := hwCap{}
222 if runtime.GOOS != "linux" {
223 // Don't try to read Linux-specific /proc files.
224 return c, fmt.Errorf("readHwCap only supported on linux, not %s", runtime.GOOS)
225 }
226 227 auxv, err := os.ReadFile(auxvFilepath)
228 if err != nil {
229 return c, fmt.Errorf("failed to read file %s: %w", auxvFilepath, err)
230 }
231 232 l := len(auxv) / 16
233 for i := 0; i < l; i++ {
234 tag := binary.LittleEndian.Uint64(auxv[i*16:])
235 val := binary.LittleEndian.Uint64(auxv[i*16+8:])
236 if tag == _AT_HWCAP {
237 c.hwCap1 = val
238 } else if tag == _AT_HWCAP2 {
239 c.hwCap2 = val
240 }
241 242 if (c.hwCap1 != 0) && (c.hwCap2 != 0) {
243 break
244 }
245 }
246 return c, nil
247 }
248 249 func initHWCap() {
250 c, err := readHWCap("/proc/self/auxv")
251 if err != nil {
252 log.Warningf("cpuid HWCap not initialized: %w", err)
253 } else {
254 hostFeatureSet.hwCap = c
255 }
256 }
257 258 var initOnce sync.Once
259 260 // Initialize initializes the global data structures used by this package.
261 // Must be called prior to using anything else in this package.
262 func Initialize() {
263 initOnce.Do(archInitialize)
264 }
265