github.com/noisysockets/netstack@v0.6.0/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/noisysockets/netstack/pkg/log"
    38  	"github.com/noisysockets/netstack/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  }