diff --git a/pkg/cpuid/BUILD b/pkg/cpuid/BUILD index c0c864902..85eced0dc 100644 --- a/pkg/cpuid/BUILD +++ b/pkg/cpuid/BUILD @@ -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"], diff --git a/pkg/cpuid/cpuid.go b/pkg/cpuid/cpuid.go index e1ea9b32c..8e3390f07 100644 --- a/pkg/cpuid/cpuid.go +++ b/pkg/cpuid/cpuid.go @@ -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) } diff --git a/pkg/cpuid/cpuid_amd64.go b/pkg/cpuid/cpuid_amd64.go new file mode 100644 index 000000000..51beeee2d --- /dev/null +++ b/pkg/cpuid/cpuid_amd64.go @@ -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 +} diff --git a/pkg/cpuid/cpuid_amd64_test.go b/pkg/cpuid/cpuid_amd64_test.go new file mode 100644 index 000000000..a5bf8ada3 --- /dev/null +++ b/pkg/cpuid/cpuid_amd64_test.go @@ -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()) + } +} diff --git a/pkg/cpuid/cpuid_arm64.go b/pkg/cpuid/cpuid_arm64.go index 446d3db83..e637bd347 100644 --- a/pkg/cpuid/cpuid_arm64.go +++ b/pkg/cpuid/cpuid_arm64.go @@ -18,239 +18,59 @@ package cpuid import ( - "bytes" - "encoding/binary" "fmt" - "io/ioutil" - "strconv" - "strings" - - "gvisor.dev/gvisor/pkg/log" + "io" ) -// ARM64 doesn't have a 'cpuid' equivalent, which means it have no architected -// discovery mechanism for hardware features available to userspace code at EL0. -// The kernel exposes the presence of these features to userspace through a set -// of flags(HWCAP/HWCAP2) bits, exposed in the auxilliary vector. -// Ref Documentation/arm64/elf_hwcaps.rst for more info. +// FeatureSet for ARM64 is defined as a static set of bits. +// +// ARM64 doesn't have a CPUID equivalent, which means it has no architected +// discovery mechanism for hardware features available to userspace code at +// EL0. The kernel exposes the presence of these features to userspace through +// a set of flags(HWCAP/HWCAP2) bits, exposed in the auxiliary vector. See +// Documentation/arm64/elf_hwcaps.rst for more info. // // Currently, only the HWCAP bits are supported. - -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 -) - -// ELF auxiliary vector tags -const ( - _AT_NULL = 0 // End of vector - _AT_HWCAP = 16 // hardware capability bit vector - _AT_HWCAP2 = 26 // hardware capability bit vector 2 -) - -// These should not be changed after they are initialized. -var hwCap uint - -// To make emulation of /proc/cpuinfo easy, these names match the names of the -// basic features in Linux defined in arch/arm64/kernel/cpuinfo.c. -var arm64FeatureStrings = map[Feature]string{ - ARM64FeatureFP: "fp", - ARM64FeatureASIMD: "asimd", - ARM64FeatureEVTSTRM: "evtstrm", - ARM64FeatureAES: "aes", - ARM64FeaturePMULL: "pmull", - ARM64FeatureSHA1: "sha1", - ARM64FeatureSHA2: "sha2", - ARM64FeatureCRC32: "crc32", - ARM64FeatureATOMICS: "atomics", - ARM64FeatureFPHP: "fphp", - ARM64FeatureASIMDHP: "asimdhp", - ARM64FeatureCPUID: "cpuid", - ARM64FeatureASIMDRDM: "asimdrdm", - ARM64FeatureJSCVT: "jscvt", - ARM64FeatureFCMA: "fcma", - ARM64FeatureLRCPC: "lrcpc", - ARM64FeatureDCPOP: "dcpop", - ARM64FeatureSHA3: "sha3", - ARM64FeatureSM3: "sm3", - ARM64FeatureSM4: "sm4", - ARM64FeatureASIMDDP: "asimddp", - ARM64FeatureSHA512: "sha512", - ARM64FeatureSVE: "sve", - ARM64FeatureASIMDFHM: "asimdfhm", -} - -var ( +type FeatureSet struct { + hwCap uint cpuFreqMHz float64 cpuImplHex uint64 cpuArchDec uint64 cpuVarHex uint64 cpuPartHex uint64 cpuRevDec uint64 -) - -// arm64FeaturesFromString includes features from arm64FeatureStrings. -var arm64FeaturesFromString = make(map[string]Feature) - -// 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) { - f, b := arm64FeaturesFromString[s] - return f, b } -// String implements fmt.Stringer. -func (f Feature) String() string { - if s := f.flagString(); s != "" { - return s - } - - return fmt.Sprintf("", f) +// CPUImplementer is part of the processor signature. +func (fs FeatureSet) CPUImplementer() uint8 { + return uint8(fs.cpuImplHex) } -func (f Feature) flagString() string { - if s, ok := arm64FeatureStrings[f]; ok { - return s - } - - return "" +// CPUArchitecture is part of the processor signature. +func (fs FeatureSet) CPUArchitecture() uint8 { + return uint8(fs.cpuArchDec) } -// FeatureSet is a set of Features for a CPU. -// -// +stateify savable -type FeatureSet struct { - // Set is the set of features that are enabled in this FeatureSet. - Set map[Feature]bool - - // CPUImplementer is part of the processor signature. - CPUImplementer uint8 - - // CPUArchitecture is part of the processor signature. - CPUArchitecture uint8 - - // CPUVariant is part of the processor signature. - CPUVariant uint8 - - // CPUPartnum is part of the processor signature. - CPUPartnum uint16 - - // CPURevision is part of the processor signature. - CPURevision uint8 +// CPUVariant is part of the processor signature. +func (fs FeatureSet) CPUVariant() uint8 { + return uint8(fs.cpuVarHex) } -// Clone returns a copy of fs. -func (fs *FeatureSet) Clone() *FeatureSet { - fs2 := *fs - fs2.Set = make(map[Feature]bool) - for f, b := range fs.Set { - fs2.Set[f] = b - } - return &fs2 +// CPUPartnum is part of the processor signature. +func (fs FeatureSet) CPUPartnum() uint16 { + return uint16(fs.cpuPartHex) } -// CheckHostCompatible returns nil if fs is a subset of the host feature set. -// Noop on arm64. -func (fs *FeatureSet) CheckHostCompatible() error { - return nil +// CPURevision is part of the processor signature. +func (fs FeatureSet) CPURevision() uint8 { + return uint8(fs.cpuRevDec) } // 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(NEON) registers, and other cpu state that's not // associated with the normal task context. -func (fs *FeatureSet) ExtendedStateSize() (size, align uint) { +func (fs FeatureSet) ExtendedStateSize() (size, align uint) { // ARMv8 provide 32x128bits NEON registers. // // Ref arch/arm64/include/uapi/asm/ptrace.h @@ -263,233 +83,26 @@ func (fs *FeatureSet) ExtendedStateSize() (size, align uint) { return 528, 16 } -// HasFeature tests whether or not a feature is in the given feature set. -func (fs *FeatureSet) HasFeature(feature Feature) bool { - return fs.Set[feature] -} - -// UseXsave returns true if 'fs' supports the "xsave" instruction. -// -// Irrelevant on arm64. -func (fs *FeatureSet) UseXsave() bool { - return false -} - -// FlagsString prints out supported CPU "flags" field in /proc/cpuinfo. -func (fs *FeatureSet) FlagsString() string { - var s []string - for f := range arm64FeatureStrings { - if fs.Set[f] { - if fstr := f.flagString(); fstr != "" { - s = append(s, fstr) - } - } - } - return strings.Join(s, " ") +// HasFeature checks for the presence of a feature. +func (fs FeatureSet) HasFeature(feature Feature) bool { + return fs.hwCap&(1<