// Copyright 2019 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package cpuid provides basic functionality for creating and adjusting CPU // feature sets. // // Each architecture should define its own FeatureSet type, that must be // savable, along with an allFeatures map, appropriate arch hooks and a // HostFeatureSet function. This file contains common functionality to all // architectures, which is essentially string munging and some errors. // // Individual architectures may export methods on FeatureSet that are relevant, // e.g. FeatureSet.Vendor(). Common to all architectures, FeatureSets include // HasFeature, which provides a trivial mechanism to test for the presence of // specific hardware features. The hardware features are also defined on a // per-architecture basis. package cpuid import ( "encoding/binary" "fmt" "os" "runtime" "strings" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sync" ) // contextID is the package for anyContext.Context.Value keys. type contextID int const ( // CtxFeatureSet is the FeatureSet for the context. CtxFeatureSet contextID = iota // hardware capability bit vector. _AT_HWCAP = 16 // hardware capability bit vector 2. _AT_HWCAP2 = 26 ) // anyContext represents context.Context. type anyContext interface { Value(key any) any } // FromContext returns the FeatureSet from the context, if available. func FromContext(ctx anyContext) FeatureSet { v := ctx.Value(CtxFeatureSet) if v == nil { return FeatureSet{} // Panics if used. } return v.(FeatureSet) } // Feature is a unique identifier for a particular cpu feature. We just use an // int as a feature number on x86 and arm64. // // On x86, features are numbered according to "blocks". Each block is 32 bits, and // feature bits from the same source (cpuid leaf/level) are in the same block. // // On arm64, features are numbered according to the ELF HWCAP definition, from // arch/arm64/include/uapi/asm/hwcap.h. type Feature int // allFeatureInfo is the value for allFeatures. type allFeatureInfo struct { // displayName is the short display name for the feature. displayName string // shouldAppear indicates whether the feature normally appears in // cpuinfo. This affects FlagString only. shouldAppear bool } // String implements fmt.Stringer.String. func (f Feature) String() string { info, ok := allFeatures[f] if ok { return info.displayName } return fmt.Sprintf("[0x%x?]", int(f)) // No given name. } // reverseMap is a map from displayName to Feature. var reverseMap = func() map[string]Feature { m := make(map[string]Feature) for feature, info := range allFeatures { if info.displayName != "" { // Sanity check that the name is unique. if old, ok := m[info.displayName]; ok { panic(fmt.Sprintf("feature %v has conflicting values (0x%x vs 0x%x)", info.displayName, old, feature)) } m[info.displayName] = feature } } return m }() // FeatureFromString returns the Feature associated with the given feature // string plus a bool to indicate if it could find the feature. func FeatureFromString(s string) (Feature, bool) { feature, ok := reverseMap[s] return feature, ok } // AllFeatures returns the full set of all possible features. func AllFeatures() (features []Feature) { archFlagOrder(func(f Feature) { features = append(features, f) }) return } // Subtract returns the features present in fs that are not present in other. // If all features in fs are present in other, Subtract returns nil. // // This does not check for any kinds of incompatibility. func (fs FeatureSet) Subtract(other FeatureSet) (left map[Feature]struct{}) { for feature := range allFeatures { thisHas := fs.HasFeature(feature) otherHas := other.HasFeature(feature) if thisHas && !otherHas { if left == nil { left = make(map[Feature]struct{}) } left[feature] = struct{}{} } } return } // FlagString prints out supported CPU flags. func (fs FeatureSet) FlagString() string { var s []string archFlagOrder(func(feature Feature) { if !fs.HasFeature(feature) { return } info := allFeatures[feature] if !info.shouldAppear { return } s = append(s, info.displayName) }) return strings.Join(s, " ") } // ErrIncompatible is returned for incompatible feature sets. type ErrIncompatible struct { reason string } // Error implements error.Error. func (e *ErrIncompatible) Error() string { return fmt.Sprintf("incompatible FeatureSet: %v", e.reason) } // CheckHostCompatible returns nil if fs is a subset of the host feature set. func (fs FeatureSet) CheckHostCompatible() error { hfs := HostFeatureSet() // Check that hfs is a superset of fs. if diff := fs.Subtract(hfs); len(diff) > 0 { return &ErrIncompatible{ reason: fmt.Sprintf("missing features: %v", diff), } } // Make arch-specific checks. return fs.archCheckHostCompatible(hfs) } // +stateify savable type hwCap struct { // hwCap1 stores HWCAP bits exposed through the elf auxiliary vector. hwCap1 uint64 // hwCap2 stores HWCAP2 bits exposed through the elf auxiliary vector. hwCap2 uint64 } // The auxiliary vector of a process on the Linux system can be read // from /proc/self/auxv, and tags and values are stored as 8-bytes // decimal key-value pairs on the 64-bit system. // // $ od -t d8 /proc/self/auxv // // 0000000 33 140734615224320 // 0000020 16 3219913727 // 0000040 6 4096 // 0000060 17 100 // 0000100 3 94665627353152 // 0000120 4 56 // 0000140 5 9 // 0000160 7 140425502162944 // 0000200 8 0 // 0000220 9 94665627365760 // 0000240 11 1000 // 0000260 12 1000 // 0000300 13 1000 // 0000320 14 1000 // 0000340 23 0 // 0000360 25 140734614619513 // 0000400 26 0 // 0000420 31 140734614626284 // 0000440 15 140734614619529 // 0000460 0 0 func readHWCap(auxvFilepath string) (hwCap, error) { c := hwCap{} if runtime.GOOS != "linux" { // Don't try to read Linux-specific /proc files. return c, fmt.Errorf("readHwCap only supported on linux, not %s", runtime.GOOS) } auxv, err := os.ReadFile(auxvFilepath) if err != nil { return c, fmt.Errorf("failed to read file %s: %w", auxvFilepath, err) } l := len(auxv) / 16 for i := 0; i < l; i++ { tag := binary.LittleEndian.Uint64(auxv[i*16:]) val := binary.LittleEndian.Uint64(auxv[i*16+8:]) if tag == _AT_HWCAP { c.hwCap1 = val } else if tag == _AT_HWCAP2 { c.hwCap2 = val } if (c.hwCap1 != 0) && (c.hwCap2 != 0) { break } } return c, nil } func initHWCap() { c, err := readHWCap("/proc/self/auxv") if err != nil { log.Warningf("cpuid HWCap not initialized: %w", err) } else { hostFeatureSet.hwCap = c } } var initOnce sync.Once // Initialize initializes the global data structures used by this package. // Must be called prior to using anything else in this package. func Initialize() { initOnce.Do(archInitialize) }