github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/cpuid/cpuid.go (about) 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 "github.com/MerlinKodo/gvisor/pkg/log" 38 "github.com/MerlinKodo/gvisor/pkg/sync" 39 ) 40 41 // contextID is the package for context.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 // context represents context.Context. 55 type context interface { 56 Value(key any) any 57 } 58 59 // FromContext returns the FeatureSet from the context, if available. 60 func FromContext(ctx context) 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 }