Refactor CPUID to allow for use in KVM and ring0.

Updates #5039

PiperOrigin-RevId: 421696994
This commit is contained in:
Adin Scannell
2022-01-13 17:24:28 -08:00
committed by gVisor bot
parent 8b8a181868
commit 266cabd008
64 changed files with 2419 additions and 2188 deletions
+16 -6
View File
@@ -5,21 +5,29 @@ package(licenses = ["notice"])
go_library(
name = "cpuid",
srcs = [
"cpu_amd64.s",
"cpuid.go",
"cpuid_amd64.go",
"cpuid_arm64.go",
"cpuid_x86.go",
"features_amd64.go",
"features_arm64.go",
"native_amd64.go",
"native_amd64.s",
"native_arm64.go",
"static_amd64.go",
],
visibility = ["//:sandbox"],
deps = ["//pkg/log"],
deps = [
"//pkg/context",
"//pkg/log",
],
)
go_test(
name = "cpuid_test",
size = "small",
srcs = [
"cpuid_arm64_test.go",
"cpuid_x86_test.go",
"cpuid_amd64_test.go",
"cpuid_test.go",
],
library = ":cpuid",
)
@@ -28,7 +36,9 @@ go_test(
name = "cpuid_parse_test",
size = "small",
srcs = [
"cpuid_parse_x86_test.go",
"cpuid_parse_amd64_test.go",
"cpuid_parse_arm64_test.go",
"cpuid_parse_test.go",
],
library = ":cpuid",
tags = ["manual"],
+135 -22
View File
@@ -15,43 +15,156 @@
// Package cpuid provides basic functionality for creating and adjusting CPU
// feature sets.
//
// To use FeatureSets, one should start with an existing FeatureSet (either a
// known platform, or HostFeatureSet()) and then add, remove, and test for
// features as desired.
// 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.
//
// For example: on x86, test for hardware extended state saving, and if
// we don't have it, don't expose AVX, which cannot be saved with fxsave.
//
// if !HostFeatureSet().HasFeature(X86FeatureXSAVE) {
// exposedFeatures.Remove(X86FeatureAVX)
// }
// 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 (
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/context"
)
// contextID is the package for context.Context.Value keys.
type contextID int
const (
// CtxFeatureSet is the FeatureSet for the context.
CtxFeatureSet contextID = iota
)
// FromContext returns the FeatureSet from the context, if available.
func FromContext(ctx context.Context) 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.
// arch/arm64/include/uapi/asm/hwcap.h
// On arm64, features are numbered according to the ELF HWCAP definition, from:
// arch/arm64/include/uapi/asm/hwcap.h
type Feature int
// HostFeatureSet returns a FeatureSet that matches that of the host machine.
// Callers must not mutate the returned FeatureSet.
func HostFeatureSet() *FeatureSet {
return hostFeatureSet
// 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
}
var hostFeatureSet *FeatureSet
// 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.
}
// ErrIncompatible is returned by FeatureSet.HostCompatible if fs is not a
// subset of the host feature set.
// 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 {
message string
reason string
}
// Error implements error.
func (e ErrIncompatible) Error() string {
return e.message
// 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)
}
+420
View File
@@ -0,0 +1,420 @@
// 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.
//go:build amd64
// +build amd64
package cpuid
import (
"fmt"
"io"
)
// FeatureSet defines features in terms of CPUID leaves and bits.
//
// Common references:
//
// Intel:
// * Intel SDM Volume 2, Chapter 3.2 "CPUID" (more up-to-date)
// * Intel Application Note 485 (more detailed)
//
// AMD:
// * AMD64 APM Volume 3, Appendix 3 "Obtaining Processor Information ..."
//
// +stateify savable
type FeatureSet struct {
// Function is the underlying CPUID Function.
//
// This is exported to allow direct calls of the underlying CPUID
// function, where required.
Function `state:".(Static)"`
}
// saveFunction saves the function as a static query.
func (fs *FeatureSet) saveFunction() Static {
if s, ok := fs.Function.(Static); ok {
return s
}
return fs.ToStatic()
}
// loadFunction saves the function as a static query.
func (fs *FeatureSet) loadFunction(s Static) {
fs.Function = s
}
// Helper to convert 3 regs into 12-byte vendor ID.
//
//go:nosplit
func vendorIDFromRegs(bx, cx, dx uint32) (r [12]byte) {
for i := uint(0); i < 4; i++ {
b := byte(bx >> (i * 8))
r[i] = b
}
for i := uint(0); i < 4; i++ {
b := byte(dx >> (i * 8))
r[4+i] = b
}
for i := uint(0); i < 4; i++ {
b := byte(cx >> (i * 8))
r[8+i] = b
}
return r
}
// Helper to merge a 12-byte vendor ID back to registers.
//
// Used by static_amd64.go.
func regsFromVendorID(r [12]byte) (bx, cx, dx uint32) {
bx |= uint32(r[0])
bx |= uint32(r[1]) << 8
bx |= uint32(r[2]) << 16
bx |= uint32(r[3]) << 24
cx |= uint32(r[4])
cx |= uint32(r[5]) << 8
cx |= uint32(r[6]) << 16
cx |= uint32(r[7]) << 24
dx |= uint32(r[8])
dx |= uint32(r[9]) << 8
dx |= uint32(r[10]) << 16
dx |= uint32(r[10]) << 24
return
}
// VendorID is the 12-char string returned in ebx:edx:ecx for eax=0.
//
//go:nosplit
func (fs FeatureSet) VendorID() [12]byte {
_, bx, cx, dx := fs.query(vendorID)
return vendorIDFromRegs(bx, cx, dx)
}
// Helper to deconstruct signature dword.
//
//go:nosplit
func signatureSplit(v uint32) (ef, em, pt, f, m, sid uint8) {
sid = uint8(v & 0xf)
m = uint8(v>>4) & 0xf
f = uint8(v>>8) & 0xf
pt = uint8(v>>12) & 0x3
em = uint8(v>>16) & 0xf
ef = uint8(v >> 20)
return
}
// ExtendedFamily is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) ExtendedFamily() uint8 {
ax, _, _, _ := fs.query(featureInfo)
ef, _, _, _, _, _ := signatureSplit(ax)
return ef
}
// ExtendedModel is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) ExtendedModel() uint8 {
ax, _, _, _ := fs.query(featureInfo)
_, em, _, _, _, _ := signatureSplit(ax)
return em
}
// ProcessorType is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) ProcessorType() uint8 {
ax, _, _, _ := fs.query(featureInfo)
_, _, pt, _, _, _ := signatureSplit(ax)
return pt
}
// Family is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) Family() uint8 {
ax, _, _, _ := fs.query(featureInfo)
_, _, _, f, _, _ := signatureSplit(ax)
return f
}
// Model is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) Model() uint8 {
ax, _, _, _ := fs.query(featureInfo)
_, _, _, _, m, _ := signatureSplit(ax)
return m
}
// SteppingID is part of the processor signature.
//
//go:nosplit
func (fs FeatureSet) SteppingID() uint8 {
ax, _, _, _ := fs.query(featureInfo)
_, _, _, _, _, sid := signatureSplit(ax)
return sid
}
// VirtualAddressBits returns the number of bits available for virtual
// addresses.
//
//go:nosplit
func (fs FeatureSet) VirtualAddressBits() uint32 {
ax, _, _, _ := fs.query(addressSizes)
return (ax >> 8) & 0xff
}
// PhysicalAddressBits returns the number of bits available for physical
// addresses.
//
//go:nosplit
func (fs FeatureSet) PhysicalAddressBits() uint32 {
ax, _, _, _ := fs.query(addressSizes)
return ax & 0xff
}
// CacheType describes the type of a cache, as returned in eax[4:0] for eax=4.
type CacheType uint8
const (
// cacheNull indicates that there are no more entries.
cacheNull CacheType = iota
// CacheData is a data cache.
CacheData
// CacheInstruction is an instruction cache.
CacheInstruction
// CacheUnified is a unified instruction and data cache.
CacheUnified
)
// Cache describes the parameters of a single cache on the system.
//
// This is returned by the Caches method on FeatureSet.
type Cache struct {
// Level is the hierarchical level of this cache (L1, L2, etc).
Level uint32
// Type is the type of cache.
Type CacheType
// FullyAssociative indicates that entries may be placed in any block.
FullyAssociative bool
// Partitions is the number of physical partitions in the cache.
Partitions uint32
// Ways is the number of ways of associativity in the cache.
Ways uint32
// Sets is the number of sets in the cache.
Sets uint32
// InvalidateHierarchical indicates that WBINVD/INVD from threads
// sharing this cache acts upon lower level caches for threads sharing
// this cache.
InvalidateHierarchical bool
// Inclusive indicates that this cache is inclusive of lower cache
// levels.
Inclusive bool
// DirectMapped indicates that this cache is directly mapped from
// address, rather than using a hash function.
DirectMapped bool
}
// Caches describes the caches on the CPU.
//
// Only supported on Intel; requires allocation.
func (fs FeatureSet) Caches() (caches []Cache) {
if !fs.Intel() {
return
}
// Check against the cache line, which should be consistent.
cacheLine := fs.CacheLine()
for i := uint32(0); ; i++ {
out := fs.Query(In{
Eax: uint32(intelDeterministicCacheParams),
Ecx: i,
})
t := CacheType(out.Eax & 0xf)
if t == cacheNull {
break
}
lineSize := (out.Ebx & 0xfff) + 1
if lineSize != cacheLine {
panic(fmt.Sprintf("Mismatched cache line size: %d vs %d", lineSize, cacheLine))
}
caches = append(caches, Cache{
Type: t,
Level: (out.Eax >> 5) & 0x7,
FullyAssociative: ((out.Eax >> 9) & 1) == 1,
Partitions: ((out.Ebx >> 12) & 0x3ff) + 1,
Ways: ((out.Ebx >> 22) & 0x3ff) + 1,
Sets: out.Ecx + 1,
InvalidateHierarchical: (out.Edx & 1) == 0,
Inclusive: ((out.Edx >> 1) & 1) == 1,
DirectMapped: ((out.Edx >> 2) & 1) == 0,
})
}
return
}
// CacheLine is the size of a cache line in bytes.
//
// All caches use the same line size. This is not enforced in the CPUID
// encoding, but is true on all known x86 processors.
//
//go:nosplit
func (fs FeatureSet) CacheLine() uint32 {
_, bx, _, _ := fs.query(featureInfo)
return 8 * (bx >> 8) & 0xff
}
// HasFeature tests whether or not a feature is in the given feature set.
//
// This function is safe to call from a nosplit context, as long as the
// FeatureSet does not have any masked features.
//
//go:nosplit
func (fs FeatureSet) HasFeature(feature Feature) bool {
return feature.check(fs)
}
// WriteCPUInfoTo is to generate a section of one cpu in /proc/cpuinfo. This is
// a minimal /proc/cpuinfo, it is missing some fields like "microcode" that are
// not always printed in Linux. The bogomips field is simply made up.
func (fs FeatureSet) WriteCPUInfoTo(cpu uint, w io.Writer) {
// Avoid many redunant calls here, since this can occasionally appear
// in the hot path. Read all basic information up front, see above.
ax, _, _, _ := fs.query(featureInfo)
ef, em, _, f, m, _ := signatureSplit(ax)
vendor := fs.VendorID()
fmt.Fprintf(w, "processor\t: %d\n", cpu)
fmt.Fprintf(w, "vendor_id\t: %s\n", string(vendor[:]))
fmt.Fprintf(w, "cpu family\t: %d\n", ((ef<<4)&0xff)|f)
fmt.Fprintf(w, "model\t\t: %d\n", ((em<<4)&0xff)|m)
fmt.Fprintf(w, "model name\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(w, "stepping\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(w, "cpu MHz\t\t: %.3f\n", cpuFreqMHz)
fmt.Fprintf(w, "fpu\t\t: yes\n")
fmt.Fprintf(w, "fpu_exception\t: yes\n")
fmt.Fprintf(w, "cpuid level\t: %d\n", uint32(xSaveInfo)) // Same as ax in vendorID.
fmt.Fprintf(w, "wp\t\t: yes\n")
fmt.Fprintf(w, "flags\t\t: %s\n", fs.FlagString())
fmt.Fprintf(w, "bogomips\t: %.02f\n", cpuFreqMHz) // It's bogus anyway.
fmt.Fprintf(w, "clflush size\t: %d\n", fs.CacheLine())
fmt.Fprintf(w, "cache_alignment\t: %d\n", fs.CacheLine())
fmt.Fprintf(w, "address sizes\t: %d bits physical, %d bits virtual\n", 46, 48)
fmt.Fprintf(w, "power management:\n") // This is always here, but can be blank.
fmt.Fprintf(w, "\n") // The /proc/cpuinfo file ends with an extra newline.
}
var (
authenticAMD = [12]byte{'A', 'u', 't', 'h', 'e', 'n', 't', 'i', 'c', 'A', 'M', 'D'}
genuineIntel = [12]byte{'G', 'e', 'n', 'u', 'i', 'n', 'e', 'I', 'n', 't', 'e', 'l'}
)
// AMD returns true if fs describes an AMD CPU.
//
//go:nosplit
func (fs FeatureSet) AMD() bool {
return fs.VendorID() == authenticAMD
}
// Intel returns true if fs describes an Intel CPU.
//
//go:nosplit
func (fs FeatureSet) Intel() bool {
return fs.VendorID() == genuineIntel
}
// Leaf 0 of xsaveinfo function returns the size for currently
// enabled xsave features in ebx, the maximum size if all valid
// features are saved with xsave in ecx, and valid XCR0 bits in
// edx:eax.
//
// If xSaveInfo isn't supported, cpuid will not fault but will
// return bogus values.
var maxXsaveSize = native(In{Eax: uint32(xSaveInfo)}).Ecx
// ExtendedStateSize returns the number of bytes needed to save the "extended
// state" for this processor and the boundary it must be aligned to. Extended
// state includes floating point registers, and other cpu state that's not
// associated with the normal task context.
//
// Note: We can save some space here with an optimization where we use a
// smaller chunk of memory depending on features that are actually enabled.
// Currently we just use the largest possible size for simplicity (which is
// about 2.5K worst case, with avx512).
//
//go:nosplit
func (fs FeatureSet) ExtendedStateSize() (size, align uint) {
if fs.UseXsave() {
return uint(maxXsaveSize), 64
}
// If we don't support xsave, we fall back to fxsave, which requires
// 512 bytes aligned to 16 bytes.
return 512, 16
}
// ValidXCR0Mask returns the valid bits in control register XCR0.
//
//go:nosplit
func (fs FeatureSet) ValidXCR0Mask() uint64 {
if !fs.HasFeature(X86FeatureXSAVE) {
return 0
}
ax, _, _, dx := fs.query(xSaveInfo)
return uint64(dx)<<32 | uint64(ax)
}
// UseXsave returns the choice of fp state saving instruction.
//
//go:nosplit
func (fs FeatureSet) UseXsave() bool {
return fs.HasFeature(X86FeatureXSAVE) && fs.HasFeature(X86FeatureOSXSAVE)
}
// UseXsaveopt returns true if 'fs' supports the "xsaveopt" instruction.
//
//go:nosplit
func (fs FeatureSet) UseXsaveopt() bool {
return fs.UseXsave() && fs.HasFeature(X86FeatureXSAVEOPT)
}
// archCheckHostCompatible checks for compatibility.
func (fs FeatureSet) archCheckHostCompatible(hfs FeatureSet) error {
// The size of a cache line must match, as it is critical to correctly
// utilizing CLFLUSH. Other cache properties are allowed to change, as
// they are not important to correctness.
fsCache := fs.CacheLine()
hostCache := hfs.CacheLine()
if fsCache != hostCache {
return &ErrIncompatible{
reason: fmt.Sprintf("CPU cache line size %d incompatible with host cache line size %d", fsCache, hostCache),
}
}
return nil
}
+95
View File
@@ -0,0 +1,95 @@
// 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.
//go:build amd64
// +build amd64
package cpuid
import (
"testing"
)
// makeFatureSet creates a new FeatureSet.
func makeFeatureSet(features ...Feature) FeatureSet {
s := make(Static)
for _, f := range features {
s.Add(f)
}
return FeatureSet{
Function: s,
}
}
var (
justFPU = makeFeatureSet(X86FeatureFPU)
justFPUandPAE = makeFeatureSet(X86FeatureFPU, X86FeaturePAE)
)
func TestSubtract(t *testing.T) {
if left := justFPU.Subtract(justFPUandPAE); len(left) > 0 {
t.Errorf("Got %q is not subset of %q, want left (%v) to be non-empty", justFPU.FlagString(), justFPUandPAE.FlagString(), left)
}
if left := justFPUandPAE.Subtract(justFPU); len(left) == 0 {
t.Errorf("Got %q is a subset of %q, want left (%v) to be empty", justFPU.FlagString(), justFPUandPAE.FlagString(), left)
}
}
// TODO(b/73346484): Run this test on a very old platform, and make sure more
// bits are enabled than just FPU and PAE. This test currently may not detect
// if HostFeatureSet gives back junk bits.
func TestHostFeatureSet(t *testing.T) {
hostFeatures := HostFeatureSet()
if justFPUandPAE.Subtract(hostFeatures) != nil {
t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures)
}
}
func TestHasFeature(t *testing.T) {
if !justFPU.HasFeature(X86FeatureFPU) {
t.Errorf("HasFeature failed, %q should contain %v", justFPU.FlagString(), X86FeatureFPU)
}
if justFPU.HasFeature(X86FeatureAVX) {
t.Errorf("HasFeature failed, %q should not contain %v", justFPU.FlagString(), X86FeatureAVX)
}
}
func TestAdd(t *testing.T) {
// Test a basic insertion into the FeatureSet.
testFeatures := makeFeatureSet(X86FeatureCLFSH)
if !testFeatures.HasFeature(X86FeatureCLFSH) {
t.Errorf("Add failed, got %v want set with %v", testFeatures, X86FeatureCLFSH)
}
// Test that duplicates are ignored.
testFeatures.Function.(Static).Add(X86FeatureCLFSH)
if !testFeatures.HasFeature(X86FeatureCLFSH) {
t.Errorf("Duplicate add removed entry, got %v want set with %v", testFeatures, X86FeatureCLFSH)
}
}
func TestRemove(t *testing.T) {
// Try removing the last feature.
testFeatures := makeFeatureSet(X86FeatureFPU, X86FeaturePAE)
testFeatures.Function.(Static).Remove(X86FeaturePAE)
if !testFeatures.HasFeature(X86FeatureFPU) || testFeatures.HasFeature(X86FeaturePAE) {
t.Errorf("Remove failed, got %q want %q", testFeatures.FlagString(), justFPU.FlagString())
}
// Try removing a feature not in the set.
testFeatures.Function.(Static).Remove(X86FeatureRDRAND)
if !testFeatures.HasFeature(X86FeatureFPU) {
t.Errorf("Remove failed, got %q want %q", testFeatures.FlagString(), justFPU.FlagString())
}
}
+42 -429
View File
File diff suppressed because it is too large Load Diff
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2020 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.
//go:build arm64
// +build arm64
package cpuid
import (
"testing"
)
var justFP = &FeatureSet{
Set: map[Feature]bool{
ARM64FeatureFP: true,
}}
func TestHostFeatureSet(t *testing.T) {
hostFeatures := HostFeatureSet()
if len(hostFeatures.Set) == 0 {
t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures)
}
}
func TestHasFeature(t *testing.T) {
if !justFP.HasFeature(ARM64FeatureFP) {
t.Errorf("HasFeature failed, %v should contain %v", justFP, ARM64FeatureFP)
}
if justFP.HasFeature(ARM64FeatureSM3) {
t.Errorf("HasFeature failed, %v should not contain %v", justFP, ARM64FeatureSM3)
}
}
func TestFeatureFromString(t *testing.T) {
f, ok := FeatureFromString("asimd")
if f != ARM64FeatureASIMD || !ok {
t.Errorf("got %v want asimd", f)
}
f, ok = FeatureFromString("bad")
if ok {
t.Errorf("got %v want nothing", f)
}
}
+58
View File
@@ -0,0 +1,58 @@
// 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.
//go:build amd64
// +build amd64
package cpuid
func archSkipFeature(feature Feature, major, minor int) bool {
switch {
// Block 0.
case feature == X86FeatureSDBG && (major < 4 || major == 4 && minor < 3):
// SDBG only exposed in
// b1c599b8ff80ea79b9f8277a3f9f36a7b0cfedce (4.3).
return true
// Block 2.
case feature == X86FeatureRDT && (major < 4 || major == 4 && minor < 10):
// RDT only exposed in
// 4ab1586488cb56ed8728e54c4157cc38646874d9 (4.10).
return true
// Block 3.
case feature == X86FeatureAVX512VBMI && (major < 4 || major == 4 && minor < 10):
// AVX512VBMI only exposed in
// a8d9df5a509a232a959e4ef2e281f7ecd77810d6 (4.10).
return true
case feature == X86FeatureUMIP && (major < 4 || major == 4 && minor < 15):
// UMIP only exposed in
// 3522c2a6a4f341058b8291326a945e2a2d2aaf55 (4.15).
return true
case feature == X86FeaturePKU && (major < 4 || major == 4 && minor < 9):
// PKU only exposed in
// dfb4a70f20c5b3880da56ee4c9484bdb4e8f1e65 (4.9).
return true
// Block 4.
case feature == X86FeatureXSAVES && (major < 4 || major == 4 && minor < 8):
// XSAVES only exposed in
// b8be15d588060a03569ac85dc4a0247460988f5b (4.8).
return true
// Block 5.
case feature == X86FeaturePERFCTR_LLC && (major < 4 || major == 4 && minor < 14):
// PERFCTR_LLC renamed in
// 910448bbed066ab1082b510eef1ae61bb792d854 (4.14).
return true
default:
return false
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2020 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.
//go:build arm64
// +build arm64
package cpuid
func archSkipFeature(feature Feature, major, minor int) bool {
return false
}
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build 386 || amd64
// +build 386 amd64
package cpuid
import (
@@ -64,83 +61,43 @@ func kernelVersion() (int, int, error) {
// on the host.
//
// It does *not* verify that all features reported by the host are detected by
// HostFeatureSet.
//
// i.e., test that HostFeatureSet is a subset of the host features.
// HostFeatureSet. Linux has synthetic Linux-specific features that have no
// analog in the actual CPUID feature set.
func TestHostFeatureFlags(t *testing.T) {
cpuinfoBytes, _ := ioutil.ReadFile("/proc/cpuinfo")
cpuinfo := string(cpuinfoBytes)
t.Logf("Host cpu info:\n%s", cpuinfo)
// Extract the kernel version.
major, minor, err := kernelVersion()
if err != nil {
t.Fatalf("Unable to parse kernel version: %v", err)
}
// Extract all cpuinfo flags.
cpuinfoBytes, _ := ioutil.ReadFile("/proc/cpuinfo")
cpuinfo := string(cpuinfoBytes)
re := regexp.MustCompile(`(?m)^flags\s+: (.*)$`)
m := re.FindStringSubmatch(cpuinfo)
if len(m) != 2 {
t.Fatalf("Unable to extract flags from %q", cpuinfo)
}
cpuinfoFlags := make(map[string]struct{})
for _, f := range strings.Split(m[1], " ") {
cpuinfoFlags[f] = struct{}{}
}
// Check against host flags.
fs := HostFeatureSet()
// All features have a string and appear in host cpuinfo.
for f := range fs.Set {
name := f.flagString(false)
if name == "" {
t.Errorf("Non-parsable feature: %v", f)
}
for feature, info := range allFeatures {
// Special cases not consistently visible. We don't mind if
// they are exposed in earlier versions.
switch {
// Block 0.
case f == X86FeatureSDBG && (major < 4 || major == 4 && minor < 3):
// SDBG only exposed in
// b1c599b8ff80ea79b9f8277a3f9f36a7b0cfedce (4.3).
continue
// Block 2.
case f == X86FeatureRDT && (major < 4 || major == 4 && minor < 10):
// RDT only exposed in
// 4ab1586488cb56ed8728e54c4157cc38646874d9 (4.10).
continue
// Block 3.
case f == X86FeatureAVX512VBMI && (major < 4 || major == 4 && minor < 10):
// AVX512VBMI only exposed in
// a8d9df5a509a232a959e4ef2e281f7ecd77810d6 (4.10).
continue
case f == X86FeatureUMIP && (major < 4 || major == 4 && minor < 15):
// UMIP only exposed in
// 3522c2a6a4f341058b8291326a945e2a2d2aaf55 (4.15).
continue
case f == X86FeaturePKU && (major < 4 || major == 4 && minor < 9):
// PKU only exposed in
// dfb4a70f20c5b3880da56ee4c9484bdb4e8f1e65 (4.9).
continue
// Block 4.
case f == X86FeatureXSAVES && (major < 4 || major == 4 && minor < 8):
// XSAVES only exposed in
// b8be15d588060a03569ac85dc4a0247460988f5b (4.8).
continue
// Block 5.
case f == X86FeaturePERFCTR_LLC && (major < 4 || major == 4 && minor < 14):
// PERFCTR_LLC renamed in
// 910448bbed066ab1082b510eef1ae61bb792d854 (4.14).
if archSkipFeature(feature, major, minor) {
continue
}
hidden := f.flagString(true) == ""
_, ok := cpuinfoFlags[name]
if hidden && ok {
t.Errorf("Unexpectedly hidden flag: %v", f)
} else if !hidden && !ok {
t.Errorf("Non-native flag: %v", f)
// Check against the flags.
_, ok := cpuinfoFlags[feature.String()]
if !info.shouldAppear && ok {
t.Errorf("Unexpected flag: %v", feature)
} else if info.shouldAppear && fs.HasFeature(feature) && !ok {
t.Errorf("Missing flag: %v", feature)
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2020 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
import "testing"
func TestFeatureFromString(t *testing.T) {
// Check that known features do match.
for feature, _ := range allFeatures {
f, ok := FeatureFromString(feature.String())
if f != feature || !ok {
t.Errorf("got %v, %v want %v, true", f, ok, feature)
}
}
// Check that "bad" doesn't match.
f, ok := FeatureFromString("bad")
if ok {
t.Errorf("got %v, %v want false", f, ok)
}
}
File diff suppressed because it is too large Load Diff
-244
View File
@@ -1,244 +0,0 @@
// Copyright 2018 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.
//go:build 386 || amd64
// +build 386 amd64
package cpuid
import (
"testing"
)
// These are the default values of various FeatureSet fields.
const (
defaultVendorID = "GenuineIntel"
// These processor signature defaults are derived from the values
// listed in Intel Application Note 485 for i7/Xeon processors.
defaultExtFamily uint8 = 0
defaultExtModel uint8 = 1
defaultType uint8 = 0
defaultFamily uint8 = 0x06
defaultModel uint8 = 0x0a
defaultSteppingID uint8 = 0
)
// newEmptyFeatureSet creates a new FeatureSet with a sensible default model and no features.
func newEmptyFeatureSet() *FeatureSet {
return &FeatureSet{
Set: make(map[Feature]bool),
VendorID: defaultVendorID,
ExtendedFamily: defaultExtFamily,
ExtendedModel: defaultExtModel,
ProcessorType: defaultType,
Family: defaultFamily,
Model: defaultModel,
SteppingID: defaultSteppingID,
}
}
var justFPU = &FeatureSet{
Set: map[Feature]bool{
X86FeatureFPU: true,
}}
var justFPUandPAE = &FeatureSet{
Set: map[Feature]bool{
X86FeatureFPU: true,
X86FeaturePAE: true,
}}
func TestSubtract(t *testing.T) {
if diff := justFPU.Subtract(justFPUandPAE); diff != nil {
t.Errorf("Got %v is not subset of %v, want diff (%v) to be nil", justFPU, justFPUandPAE, diff)
}
if justFPUandPAE.Subtract(justFPU) == nil {
t.Errorf("Got %v is a subset of %v, want diff to be nil", justFPU, justFPUandPAE)
}
}
// TODO(b/73346484): Run this test on a very old platform, and make sure more
// bits are enabled than just FPU and PAE. This test currently may not detect
// if HostFeatureSet gives back junk bits.
func TestHostFeatureSet(t *testing.T) {
hostFeatures := HostFeatureSet()
if justFPUandPAE.Subtract(hostFeatures) != nil {
t.Errorf("Got invalid feature set %v from HostFeatureSet()", hostFeatures)
}
}
func TestHasFeature(t *testing.T) {
if !justFPU.HasFeature(X86FeatureFPU) {
t.Errorf("HasFeature failed, %v should contain %v", justFPU, X86FeatureFPU)
}
if justFPU.HasFeature(X86FeatureAVX) {
t.Errorf("HasFeature failed, %v should not contain %v", justFPU, X86FeatureAVX)
}
}
// Note: these tests are aware of and abuse internal details of FeatureSets.
// Users of FeatureSets should not depend on this.
func TestAdd(t *testing.T) {
// Test a basic insertion into the FeatureSet.
testFeatures := newEmptyFeatureSet()
testFeatures.Add(X86FeatureCLFSH)
if len(testFeatures.Set) != 1 {
t.Errorf("Got length %v want 1", len(testFeatures.Set))
}
if !testFeatures.HasFeature(X86FeatureCLFSH) {
t.Errorf("Add failed, got %v want set with %v", testFeatures, X86FeatureCLFSH)
}
// Test that duplicates are ignored.
testFeatures.Add(X86FeatureCLFSH)
if len(testFeatures.Set) != 1 {
t.Errorf("Got length %v, want 1", len(testFeatures.Set))
}
}
func TestRemove(t *testing.T) {
// Try removing the last feature.
testFeatures := newEmptyFeatureSet()
testFeatures.Add(X86FeatureFPU)
testFeatures.Add(X86FeaturePAE)
testFeatures.Remove(X86FeaturePAE)
if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 || testFeatures.HasFeature(X86FeaturePAE) {
t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU)
}
// Try removing a feature not in the set.
testFeatures.Remove(X86FeatureRDRAND)
if !testFeatures.HasFeature(X86FeatureFPU) || len(testFeatures.Set) != 1 {
t.Errorf("Remove failed, got %v want %v", testFeatures, justFPU)
}
}
func TestFeatureFromString(t *testing.T) {
f, ok := FeatureFromString("avx")
if f != X86FeatureAVX || !ok {
t.Errorf("got %v want avx", f)
}
f, ok = FeatureFromString("bad")
if ok {
t.Errorf("got %v want nothing", f)
}
}
// This tests function 0 (eax=0), which returns the vendor ID and highest cpuid
// function reported to be available.
func TestEmulateIDVendorAndLength(t *testing.T) {
testFeatures := newEmptyFeatureSet()
ax, bx, cx, dx := testFeatures.EmulateID(0, 0)
wantEax := uint32(0xd) // Highest supported cpuid function.
// These magical constants are the characters of "GenuineIntel".
// See Intel AN485 for a reference on why they are laid out like this.
wantEbx := uint32(0x756e6547)
wantEcx := uint32(0x6c65746e)
wantEdx := uint32(0x49656e69)
if wantEax != ax {
t.Errorf("highest function failed, got %x want %x", ax, wantEax)
}
if wantEbx != bx || wantEcx != cx || wantEdx != dx {
t.Errorf("vendor string emulation failed, bx:cx:dx, got %x:%x:%x want %x:%x:%x", bx, cx, dx, wantEbx, wantEcx, wantEdx)
}
}
func TestEmulateIDBasicFeatures(t *testing.T) {
// Make a minimal test feature set.
testFeatures := newEmptyFeatureSet()
testFeatures.Add(X86FeatureCLFSH)
testFeatures.Add(X86FeatureAVX)
testFeatures.CacheLine = 64
ax, bx, cx, dx := testFeatures.EmulateID(1, 0)
ECXAVXBit := uint32(1 << uint(X86FeatureAVX))
EDXCLFlushBit := uint32(1 << uint(X86FeatureCLFSH-32)) // We adjust by 32 since it's in block 1.
if EDXCLFlushBit&dx == 0 || dx&^EDXCLFlushBit != 0 {
t.Errorf("EmulateID failed, got feature bits %x want %x", dx, testFeatures.blockMask(1))
}
if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 {
t.Errorf("EmulateID failed, got feature bits %x want %x", cx, testFeatures.blockMask(0))
}
// Default signature bits, based on values for i7/Xeon.
// See Intel AN485 for information on stepping/model bits.
defaultSignature := uint32(0x000106a0)
if defaultSignature != ax {
t.Errorf("EmulateID stepping emulation failed, got %x want %x", ax, defaultSignature)
}
clflushSizeInfo := uint32(8 << 8)
if clflushSizeInfo != bx {
t.Errorf("EmulateID bx emulation failed, got %x want %x", bx, clflushSizeInfo)
}
}
func TestEmulateIDExtendedFeatures(t *testing.T) {
// Make a minimal test feature set, one bit in each extended feature word.
testFeatures := newEmptyFeatureSet()
testFeatures.Add(X86FeatureSMEP)
testFeatures.Add(X86FeatureAVX512VBMI)
ax, bx, cx, dx := testFeatures.EmulateID(7, 0)
EBXSMEPBit := uint32(1 << uint(X86FeatureSMEP-2*32)) // Adjust by 2*32 since SMEP is a block 2 feature.
ECXAVXBit := uint32(1 << uint(X86FeatureAVX512VBMI-3*32)) // We adjust by 3*32 since it's a block 3 feature.
// Test that the desired bit is set and no other bits are set.
if EBXSMEPBit&bx == 0 || bx&^EBXSMEPBit != 0 {
t.Errorf("extended feature emulation failed, got feature bits %x want %x", bx, testFeatures.blockMask(2))
}
if ECXAVXBit&cx == 0 || cx&^ECXAVXBit != 0 {
t.Errorf("extended feature emulation failed, got feature bits %x want %x", cx, testFeatures.blockMask(3))
}
if ax != 0 || dx != 0 {
t.Errorf("extended feature emulation failed, ax:dx, got %x:%x want 0:0", ax, dx)
}
// Check that no subleaves other than 0 do anything.
ax, bx, cx, dx = testFeatures.EmulateID(7, 1)
if ax != 0 || bx != 0 || cx != 0 || dx != 0 {
t.Errorf("extended feature emulation failed, got %x:%x:%x:%x want 0:0", ax, bx, cx, dx)
}
}
// Checks that the expected extended features are available via cpuid functions
// 0x80000000 and up.
func TestEmulateIDExtended(t *testing.T) {
testFeatures := newEmptyFeatureSet()
testFeatures.Add(X86FeatureSYSCALL)
EDXSYSCALLBit := uint32(1 << uint(X86FeatureSYSCALL-6*32)) // Adjust by 6*32 since SYSCALL is a block 6 feature.
ax, bx, cx, dx := testFeatures.EmulateID(0x80000000, 0)
if ax != 0x80000001 || bx != 0 || cx != 0 || dx != 0 {
t.Errorf("EmulateID extended emulation failed, ax:bx:cx:dx, got %x:%x:%x:%x want 0x80000001:0:0:0", ax, bx, cx, dx)
}
_, _, _, dx = testFeatures.EmulateID(0x80000001, 0)
if EDXSYSCALLBit&dx == 0 || dx&^EDXSYSCALLBit != 0 {
t.Errorf("extended feature emulation failed, got feature bits %x want %x", dx, testFeatures.blockMask(6))
}
}
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
// Copyright 2020 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.
//go:build arm64
// +build arm64
package cpuid
const (
// ARM64FeatureFP indicates support for single and double precision
// float point types.
ARM64FeatureFP Feature = iota
// ARM64FeatureASIMD indicates support for Advanced SIMD with single
// and double precision float point arithmetic.
ARM64FeatureASIMD
// ARM64FeatureEVTSTRM indicates support for the generic timer
// configured to generate events at a frequency of approximately
// 100KHz.
ARM64FeatureEVTSTRM
// ARM64FeatureAES indicates support for AES instructions
// (AESE/AESD/AESMC/AESIMC).
ARM64FeatureAES
// ARM64FeaturePMULL indicates support for AES instructions
// (PMULL/PMULL2).
ARM64FeaturePMULL
// ARM64FeatureSHA1 indicates support for SHA1 instructions
// (SHA1C/SHA1P/SHA1M etc).
ARM64FeatureSHA1
// ARM64FeatureSHA2 indicates support for SHA2 instructions
// (SHA256H/SHA256H2/SHA256SU0 etc).
ARM64FeatureSHA2
// ARM64FeatureCRC32 indicates support for CRC32 instructions
// (CRC32B/CRC32H/CRC32W etc).
ARM64FeatureCRC32
// ARM64FeatureATOMICS indicates support for atomic instructions
// (LDADD/LDCLR/LDEOR/LDSET etc).
ARM64FeatureATOMICS
// ARM64FeatureFPHP indicates support for half precision float point
// arithmetic.
ARM64FeatureFPHP
// ARM64FeatureASIMDHP indicates support for ASIMD with half precision
// float point arithmetic.
ARM64FeatureASIMDHP
// ARM64FeatureCPUID indicates support for EL0 access to certain ID
// registers is available.
ARM64FeatureCPUID
// ARM64FeatureASIMDRDM indicates support for SQRDMLAH and SQRDMLSH
// instructions.
ARM64FeatureASIMDRDM
// ARM64FeatureJSCVT indicates support for the FJCVTZS instruction.
ARM64FeatureJSCVT
// ARM64FeatureFCMA indicates support for the FCMLA and FCADD
// instructions.
ARM64FeatureFCMA
// ARM64FeatureLRCPC indicates support for the LDAPRB/LDAPRH/LDAPR
// instructions.
ARM64FeatureLRCPC
// ARM64FeatureDCPOP indicates support for DC instruction (DC CVAP).
ARM64FeatureDCPOP
// ARM64FeatureSHA3 indicates support for SHA3 instructions
// (EOR3/RAX1/XAR/BCAX).
ARM64FeatureSHA3
// ARM64FeatureSM3 indicates support for SM3 instructions
// (SM3SS1/SM3TT1A/SM3TT1B).
ARM64FeatureSM3
// ARM64FeatureSM4 indicates support for SM4 instructions
// (SM4E/SM4EKEY).
ARM64FeatureSM4
// ARM64FeatureASIMDDP indicates support for dot product instructions
// (UDOT/SDOT).
ARM64FeatureASIMDDP
// ARM64FeatureSHA512 indicates support for SHA2 instructions
// (SHA512H/SHA512H2/SHA512SU0).
ARM64FeatureSHA512
// ARM64FeatureSVE indicates support for Scalable Vector Extension.
ARM64FeatureSVE
// ARM64FeatureASIMDFHM indicates support for FMLAL and FMLSL
// instructions.
ARM64FeatureASIMDFHM
)
var allFeatures = map[Feature]allFeatureInfo{
ARM64FeatureFP: {"fp", true},
ARM64FeatureASIMD: {"asimd", true},
ARM64FeatureEVTSTRM: {"evtstrm", true},
ARM64FeatureAES: {"aes", true},
ARM64FeaturePMULL: {"pmull", true},
ARM64FeatureSHA1: {"sha1", true},
ARM64FeatureSHA2: {"sha2", true},
ARM64FeatureCRC32: {"crc32", true},
ARM64FeatureATOMICS: {"atomics", true},
ARM64FeatureFPHP: {"fphp", true},
ARM64FeatureASIMDHP: {"asimdhp", true},
ARM64FeatureCPUID: {"cpuid", true},
ARM64FeatureASIMDRDM: {"asimdrdm", true},
ARM64FeatureJSCVT: {"jscvt", true},
ARM64FeatureFCMA: {"fcma", true},
ARM64FeatureLRCPC: {"lrcpc", true},
ARM64FeatureDCPOP: {"dcpop", true},
ARM64FeatureSHA3: {"sha3", true},
ARM64FeatureSM3: {"sm3", true},
ARM64FeatureSM4: {"sm4", true},
ARM64FeatureASIMDDP: {"asimddp", true},
ARM64FeatureSHA512: {"sha512", true},
ARM64FeatureSVE: {"sve", true},
ARM64FeatureASIMDFHM: {"asimdfhm", true},
}
func archFlagOrder(fn func(Feature)) {
for i := 0; i < len(allFeatures); i++ {
fn(Feature(i))
}
}
+192
View File
@@ -0,0 +1,192 @@
// 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.
//go:build amd64
// +build amd64
package cpuid
import (
"io/ioutil"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/log"
)
// cpuididFunction is a useful type wrapper.
type cpuidFunction uint32
// The constants below are the lower or "standard" cpuid functions, ordered as
// defined by the hardware. Note that these may not be included in the standard
// set of functions that we are allowed to execute, which are filtered in the
// Native.Query function defined below.
const (
vendorID cpuidFunction = 0x0 // Returns vendor ID and largest standard function.
featureInfo cpuidFunction = 0x1 // Returns basic feature bits and processor signature.
intelCacheDescriptors cpuidFunction = 0x2 // Returns list of cache descriptors. Intel only.
intelSerialNumber cpuidFunction = 0x3 // Returns processor serial number (obsolete on new hardware). Intel only.
intelDeterministicCacheParams cpuidFunction = 0x4 // Returns deterministic cache information. Intel only.
monitorMwaitParams cpuidFunction = 0x5 // Returns information about monitor/mwait instructions.
powerParams cpuidFunction = 0x6 // Returns information about power management and thermal sensors.
extendedFeatureInfo cpuidFunction = 0x7 // Returns extended feature bits.
_ // Function 0x8 is reserved.
intelDCAParams cpuidFunction = 0x9 // Returns direct cache access information. Intel only.
intelPMCInfo cpuidFunction = 0xa // Returns information about performance monitoring features. Intel only.
intelX2APICInfo cpuidFunction = 0xb // Returns core/logical processor topology. Intel only.
_ // Function 0xc is reserved.
xSaveInfo cpuidFunction = 0xd // Returns information about extended state management.
)
// The "extended" functions.
const (
extendedStart cpuidFunction = 0x80000000
extendedFunctionInfo cpuidFunction = extendedStart + 0 // Returns highest available extended function in eax.
extendedFeatures = extendedStart + 1 // Returns some extended feature bits in edx and ecx.
addressSizes = extendedStart + 8 // Physical and virtual address sizes.
)
var allowedBasicFunctions = [...]bool{
vendorID: true,
featureInfo: true,
extendedFeatureInfo: true,
intelCacheDescriptors: true,
intelDeterministicCacheParams: true,
xSaveInfo: true,
}
var allowedExtendedFunctions = [...]bool{
extendedFunctionInfo - extendedStart: true,
extendedFeatures - extendedStart: true,
addressSizes - extendedStart: true,
}
// Function executes a CPUID function.
//
// This is typically the native function or a Static definition.
type Function interface {
Query(In) Out
}
// Native is a native Function.
//
// This implements Function.
type Native struct{}
// In is input to the Query function.
//
// +stateify savable
type In struct {
Eax uint32
Ecx uint32
}
// normalize drops irrelevant Ecx values.
func (i *In) normalize() {
switch cpuidFunction(i.Eax) {
case vendorID, featureInfo, intelCacheDescriptors, extendedFunctionInfo, extendedFeatures:
i.Ecx = 0 // Ignore.
case intelDeterministicCacheParams, extendedFeatureInfo:
// Preserve i.Ecx.
}
}
// Out is output from the Query function.
//
// +stateify savable
type Out struct {
Eax uint32
Ebx uint32
Ecx uint32
Edx uint32
}
// native is the native Query function.
func native(In) Out
// Query executes CPUID natively.
//
// This implements Function.
//
//go:nosplit
func (*Native) Query(in In) Out {
if int(in.Eax) < len(allowedBasicFunctions) && allowedBasicFunctions[in.Eax] {
return native(in)
} else if in.Eax >= uint32(extendedStart) {
if l := int(in.Eax - uint32(extendedStart)); l < len(allowedExtendedFunctions) && allowedExtendedFunctions[l] {
return native(in)
}
}
return Out{} // All zeros.
}
// query is a internal wrapper.
//
//go:nosplit
func (fs FeatureSet) query(fn cpuidFunction) (uint32, uint32, uint32, uint32) {
out := fs.Query(In{Eax: uint32(fn)})
return out.Eax, out.Ebx, out.Ecx, out.Edx
}
// HostFeatureSet returns a host CPUID.
//
//go:nosplit
func HostFeatureSet() FeatureSet {
return FeatureSet{
Function: &Native{},
}
}
var (
// cpuFreqMHz is the native CPU frequency.
cpuFreqMHz float64
)
// Reads max cpu frequency from host /proc/cpuinfo. Must run before syscall
// filter installation. This value is used to create the fake /proc/cpuinfo
// from a FeatureSet.
func init() {
cpuinfob, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
// Leave it as 0... the VDSO bails out in the same way.
log.Warningf("Could not read /proc/cpuinfo: %v", err)
return
}
cpuinfo := string(cpuinfob)
// We get the value straight from host /proc/cpuinfo. On machines with
// frequency scaling enabled, this will only get the current value
// which will likely be inaccurate. This is fine on machines with
// frequency scaling disabled.
for _, line := range strings.Split(cpuinfo, "\n") {
if strings.Contains(line, "cpu MHz") {
splitMHz := strings.Split(line, ":")
if len(splitMHz) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed cpu MHz line")
return
}
// If there was a problem, leave cpuFreqMHz as 0.
var err error
cpuFreqMHz, err = strconv.ParseFloat(strings.TrimSpace(splitMHz[1]), 64)
if err != nil {
log.Warningf("Could not parse cpu MHz value %v: %v", splitMHz[1], err)
cpuFreqMHz = 0
return
}
return
}
}
log.Warningf("Could not parse /proc/cpuinfo, it is empty or does not contain cpu MHz")
}
@@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// func HostID(rax, rcx uint32) (ret0, ret1, ret2, ret3 uint32)
TEXT ·HostID(SB),$0-48
#include "textflag.h"
TEXT ·native(SB),NOSPLIT,$0-24
MOVL ax+0(FP), AX
MOVL cx+4(FP), CX
CPUID
+195
View File
@@ -0,0 +1,195 @@
// 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.
//go:build arm64
// +build arm64
package cpuid
import (
"encoding/binary"
"io/ioutil"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/log"
)
// hostFeatureSet is initialized at startup.
//
// This is copied for HostFeatureSet, below.
var hostFeatureSet FeatureSet
// HostFeatureSet returns a copy of the host FeatureSet.
func HostFeatureSet() FeatureSet {
return hostFeatureSet
}
// Fixed returns the same feature set.
func (fs FeatureSet) Fixed() FeatureSet {
return fs
}
// Reads CPU information from host /proc/cpuinfo.
//
// Must run before syscall filter installation. This value is used to create
// the fake /proc/cpuinfo from a FeatureSet.
func initCPUInfo() {
cpuinfob, err := ioutil.ReadFile("/proc/cpuinfo")
if err != nil {
// Leave everything at 0, nothing can be done.
log.Warningf("Could not read /proc/cpuinfo: %v", err)
return
}
cpuinfo := string(cpuinfob)
// We get the value straight from host /proc/cpuinfo.
for _, line := range strings.Split(cpuinfo, "\n") {
switch {
case strings.Contains(line, "BogoMIPS"):
splitMHz := strings.Split(line, ":")
if len(splitMHz) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed BogoMIPS")
break
}
// If there was a problem, leave cpuFreqMHz as 0.
var err error
hostFeatureSet.cpuFreqMHz, err = strconv.ParseFloat(strings.TrimSpace(splitMHz[1]), 64)
if err != nil {
hostFeatureSet.cpuFreqMHz = 0.0
log.Warningf("Could not parse BogoMIPS value %v: %v", splitMHz[1], err)
}
case strings.Contains(line, "CPU implementer"):
splitImpl := strings.Split(line, ":")
if len(splitImpl) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed CPU implementer")
break
}
// If there was a problem, leave cpuImplHex as 0.
var err error
hostFeatureSet.cpuImplHex, err = strconv.ParseUint(strings.TrimSpace(splitImpl[1]), 0, 64)
if err != nil {
hostFeatureSet.cpuImplHex = 0
log.Warningf("Could not parse CPU implementer value %v: %v", splitImpl[1], err)
}
case strings.Contains(line, "CPU architecture"):
splitArch := strings.Split(line, ":")
if len(splitArch) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed CPU architecture")
break
}
// If there was a problem, leave cpuArchDec as 0.
var err error
hostFeatureSet.cpuArchDec, err = strconv.ParseUint(strings.TrimSpace(splitArch[1]), 0, 64)
if err != nil {
hostFeatureSet.cpuArchDec = 0
log.Warningf("Could not parse CPU architecture value %v: %v", splitArch[1], err)
}
case strings.Contains(line, "CPU variant"):
splitVar := strings.Split(line, ":")
if len(splitVar) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed CPU variant")
break
}
// If there was a problem, leave cpuVarHex as 0.
var err error
hostFeatureSet.cpuVarHex, err = strconv.ParseUint(strings.TrimSpace(splitVar[1]), 0, 64)
if err != nil {
hostFeatureSet.cpuVarHex = 0
log.Warningf("Could not parse CPU variant value %v: %v", splitVar[1], err)
}
case strings.Contains(line, "CPU part"):
splitPart := strings.Split(line, ":")
if len(splitPart) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed CPU part")
break
}
// If there was a problem, leave cpuPartHex as 0.
var err error
hostFeatureSet.cpuPartHex, err = strconv.ParseUint(strings.TrimSpace(splitPart[1]), 0, 64)
if err != nil {
hostFeatureSet.cpuPartHex = 0
log.Warningf("Could not parse CPU part value %v: %v", splitPart[1], err)
}
case strings.Contains(line, "CPU revision"):
splitRev := strings.Split(line, ":")
if len(splitRev) < 2 {
log.Warningf("Could not read /proc/cpuinfo: malformed CPU revision")
break
}
// If there was a problem, leave cpuRevDec as 0.
var err error
hostFeatureSet.cpuRevDec, err = strconv.ParseUint(strings.TrimSpace(splitRev[1]), 0, 64)
if err != nil {
hostFeatureSet.cpuRevDec = 0
log.Warningf("Could not parse CPU revision value %v: %v", splitRev[1], err)
}
}
}
}
// 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 initHwCap() {
auxv, err := ioutil.ReadFile("/proc/self/auxv")
if err != nil {
log.Warningf("Could not read /proc/self/auxv: %v", err)
return
}
const _AT_HWCAP = 16 // hardware capability bit vector.
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 {
hostFeatureSet.hwCap = uint(val)
break
}
}
}
func init() {
initCPUInfo()
initHwCap()
}
+119
View File
@@ -0,0 +1,119 @@
// 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.
//go:build amd64
// +build amd64
package cpuid
// Static is a static CPUID function.
//
// +stateify savable
type Static map[In]Out
// Fixed converts the FeatureSet to a fixed set.
func (fs FeatureSet) Fixed() FeatureSet {
return fs.ToStatic().ToFeatureSet()
}
// ToStatic converts a FeatureSet to a Static function.
//
// You can create a new static feature set as:
//
// fs := otherFeatureSet.ToStatic().ToFeatureSet()
func (fs FeatureSet) ToStatic() Static {
s := make(Static)
// Save all allowed top-level functions.
for fn, allowed := range allowedBasicFunctions {
if allowed {
in := In{Eax: uint32(fn)}
s[in] = fs.Query(in)
}
}
// Save all allowed extended functions.
for fn, allowed := range allowedExtendedFunctions {
if allowed {
in := In{Eax: uint32(fn) + uint32(extendedStart)}
s[in] = fs.Query(in)
}
}
// Save all features (may be redundant).
for feature := range allFeatures {
feature.set(s, fs.HasFeature(feature))
}
// Save all cache information.
out := fs.Query(In{Eax: uint32(featureInfo)})
for i := uint32(0); i < out.Ecx; i++ {
in := In{Eax: uint32(intelDeterministicCacheParams), Ecx: i}
out := fs.Query(in)
s[in] = out
if CacheType(out.Eax&0xf) == cacheNull {
break
}
}
return s
}
// ToFeatureSet converts a static specification to a FeatureSet.
//
// This overloads some local values, where required.
func (s Static) ToFeatureSet() FeatureSet {
// Make a copy.
ns := make(Static)
for k, v := range s {
ns[k] = v
}
ns.normalize()
return FeatureSet{ns}
}
// afterLoad calls normalize.
func (s Static) afterLoad() {
s.normalize()
}
// normalize normalizes FPU sizes.
func (s Static) normalize() {
// Override local FPU sizes, which must be fixed.
fs := FeatureSet{s}
if fs.HasFeature(X86FeatureXSAVE) {
in := In{Eax: uint32(xSaveInfo)}
out := s[in]
out.Ecx = maxXsaveSize
s[in] = out
}
}
// Add adds a feature.
func (s Static) Add(feature Feature) Static {
feature.set(s, true)
return s
}
// Remove removes a feature.
func (s Static) Remove(feature Feature) Static {
feature.set(s, false)
return s
}
// Query implements Function.Query.
func (s Static) Query(in In) Out {
in.normalize()
return s[in]
}
+1
View File
@@ -82,5 +82,6 @@ go_library(
"//pkg/safecopy",
"//pkg/sentry/arch",
"//pkg/sentry/arch/fpu",
"//pkg/sync",
],
)
+6 -10
View File
@@ -30,6 +30,12 @@ const (
)
const (
// VirtualAddressBits is fixed at 48.
VirtualAddressBits = 48
// PhysicalAddressBits is fixed at 40.
PhysicalAddressBits = 40
// DAIF bits:debug, sError, IRQ, FIQ.
_PSR_D_BIT = 0x00000200
_PSR_A_BIT = 0x00000100
@@ -111,13 +117,3 @@ const (
PageFault Vector = El0SyncDa
VirtualizationException Vector = El0ErrBounce
)
// VirtualAddressBits returns the number bits available for virtual addresses.
func VirtualAddressBits() uint32 {
return 48
}
// PhysicalAddressBits returns the number of bits available for physical addresses.
func PhysicalAddressBits() uint32 {
return 40
}

Some files were not shown because too many files have changed in this diff Show More