cpuid.go raw

   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