diff options
Diffstat (limited to 'pkg/cpuid')
-rw-r--r-- | pkg/cpuid/BUILD | 43 | ||||
-rw-r--r-- | pkg/cpuid/cpu_amd64.s | 24 | ||||
-rw-r--r-- | pkg/cpuid/cpuid.go | 824 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_parse_test.go | 38 | ||||
-rw-r--r-- | pkg/cpuid/cpuid_test.go | 223 |
5 files changed, 1152 insertions, 0 deletions
diff --git a/pkg/cpuid/BUILD b/pkg/cpuid/BUILD new file mode 100644 index 000000000..a503b7ae8 --- /dev/null +++ b/pkg/cpuid/BUILD @@ -0,0 +1,43 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("//tools/go_stateify:defs.bzl", "go_stateify") + +go_stateify( + name = "cpuid_state", + srcs = ["cpuid.go"], + out = "cpuid_state.go", + package = "cpuid", +) + +go_library( + name = "cpuid", + srcs = [ + "cpu_amd64.s", + "cpuid.go", + "cpuid_state.go", + ], + importpath = "gvisor.googlesource.com/gvisor/pkg/cpuid", + visibility = ["//:sandbox"], + deps = [ + "//pkg/log", + "//pkg/state", + ], +) + +go_test( + name = "cpuid_test", + size = "small", + srcs = ["cpuid_test.go"], + embed = [":cpuid"], +) + +go_test( + name = "cpuid_parse_test", + size = "small", + srcs = [ + "cpuid_parse_test.go", + ], + embed = [":cpuid"], + tags = ["manual"], +) diff --git a/pkg/cpuid/cpu_amd64.s b/pkg/cpuid/cpu_amd64.s new file mode 100644 index 000000000..48a13c6fd --- /dev/null +++ b/pkg/cpuid/cpu_amd64.s @@ -0,0 +1,24 @@ +// Copyright 2018 Google Inc. +// +// 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. + +// func HostID(rax, rcx uint32) (ret0, ret1, ret2, ret3 uint32) +TEXT ·HostID(SB),$0-48 + MOVL ax+0(FP), AX + MOVL cx+4(FP), CX + CPUID + MOVL AX, ret0+8(FP) + MOVL BX, ret1+12(FP) + MOVL CX, ret2+16(FP) + MOVL DX, ret3+20(FP) + RET diff --git a/pkg/cpuid/cpuid.go b/pkg/cpuid/cpuid.go new file mode 100644 index 000000000..aa248dd98 --- /dev/null +++ b/pkg/cpuid/cpuid.go @@ -0,0 +1,824 @@ +// Copyright 2018 Google Inc. +// +// 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. + +// +build i386 amd64 + +// 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. +// +// For example: 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) +// } +package cpuid + +import ( + "bytes" + "fmt" + "io/ioutil" + "strconv" + "strings" + + "gvisor.googlesource.com/gvisor/pkg/log" +) + +// Feature is a unique identifier for a particular cpu feature. We just use an +// int as a feature number on x86. It corresponds to the bit position in the +// basic feature mask returned by a cpuid with eax=1. +type Feature int + +// block is a collection of 32 Feature bits. +type block int + +const blockSize = 32 + +// Feature bits 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. +func featureID(b block, bit int) Feature { + return Feature(32*int(b) + bit) +} + +// Block 0 constants are all of the "basic" feature bits returned by a cpuid in +// ecx with eax=1. +const ( + X86FeatureSSE3 Feature = iota + X86FeaturePCLMULDQ + X86FeatureDTES64 + X86FeatureMONITOR + X86FeatureDSCPL + X86FeatureVMX + X86FeatureSMX + X86FeatureEST + X86FeatureTM2 + X86FeatureSSSE3 // Not a typo, "supplemental" SSE3. + X86FeatureCNXTID + X86FeatureSDBG + X86FeatureFMA + X86FeatureCX16 + X86FeatureXTPR + X86FeaturePDCM + _ // ecx bit 16 is reserved. + X86FeaturePCID + X86FeatureDCA + X86FeatureSSE4_1 + X86FeatureSSE4_2 + X86FeatureX2APIC + X86FeatureMOVBE + X86FeaturePOPCNT + X86FeatureTSCD + X86FeatureAES + X86FeatureXSAVE + X86FeatureOSXSAVE + X86FeatureAVX + X86FeatureF16C + X86FeatureRDRAND + _ // ecx bit 31 is reserved. +) + +// Block 1 constants are all of the "basic" feature bits returned by a cpuid in +// edx with eax=1. +const ( + X86FeatureFPU Feature = 32 + iota + X86FeatureVME + X86FeatureDE + X86FeaturePSE + X86FeatureTSC + X86FeatureMSR + X86FeaturePAE + X86FeatureMCE + X86FeatureCX8 + X86FeatureAPIC + _ // edx bit 10 is reserved. + X86FeatureSEP + X86FeatureMTRR + X86FeaturePGE + X86FeatureMCA + X86FeatureCMOV + X86FeaturePAT + X86FeaturePSE36 + X86FeaturePSN + X86FeatureCLFSH + _ // edx bit 20 is reserved. + X86FeatureDS + X86FeatureACPI + X86FeatureMMX + X86FeatureFXSR + X86FeatureSSE + X86FeatureSSE2 + X86FeatureSS + X86FeatureHTT + X86FeatureTM + X86FeatureIA64 + X86FeaturePBE +) + +// Block 2 bits are the "structured extended" features returned in ebx for +// eax=7, ecx=0. +const ( + X86FeatureFSGSBase Feature = 2*32 + iota + X86FeatureTSC_ADJUST + _ // ebx bit 2 is reserved. + X86FeatureBMI1 + X86FeatureHLE + X86FeatureAVX2 + X86FeatureFDP_EXCPTN_ONLY + X86FeatureSMEP + X86FeatureBMI2 + X86FeatureERMS + X86FeatureINVPCID + X86FeatureRTM + X86FeatureCQM + X86FeatureFPCSDS + X86FeatureMPX + X86FeatureRDT + X86FeatureAVX512F + X86FeatureAVX512DQ + X86FeatureRDSEED + X86FeatureADX + X86FeatureSMAP + X86FeatureAVX512IFMA + X86FeaturePCOMMIT + X86FeatureCLFLUSHOPT + X86FeatureCLWB + X86FeatureIPT // Intel processor trace. + X86FeatureAVX512PF + X86FeatureAVX512ER + X86FeatureAVX512CD + X86FeatureSHA + X86FeatureAVX512BW + X86FeatureAVX512VL +) + +// Block 3 bits are the "extended" features returned in ecx for eax=7, ecx=0. +const ( + X86FeaturePREFETCHWT1 Feature = 3*32 + iota + X86FeatureAVX512VBMI + X86FeatureUMIP + X86FeaturePKU +) + +// Block 4 constants are for xsave capabilities in CPUID.(EAX=0DH,ECX=01H):EAX. +// The CPUID leaf is available only if 'X86FeatureXSAVE' is present. +const ( + X86FeatureXSAVEOPT Feature = 4*32 + iota + X86FeatureXSAVEC + X86FeatureXGETBV1 + X86FeatureXSAVES + // EAX[31:4] are reserved. +) + +// Block 5 constants are the extended feature bits in +// CPUID.(EAX=0x80000001):ECX. These are very sparse, and so the bit positions +// are assigned manually. +const ( + X86FeatureLAHF64 Feature = 5*32 + 0 + X86FeatureLZCNT Feature = 5*32 + 5 + X86FeaturePREFETCHW Feature = 5*32 + 8 +) + +// Block 6 constants are the extended feature bits in +// CPUID.(EAX=0x80000001):EDX. These are very sparse, and so the bit positions +// are assigned manually. +const ( + X86FeatureSYSCALL Feature = 6*32 + 11 + X86FeatureNX Feature = 6*32 + 20 + X86FeatureGBPAGES Feature = 6*32 + 26 + X86FeatureRDTSCP Feature = 6*32 + 27 + X86FeatureLM Feature = 6*32 + 29 + // These are not in the most recent intel manual. Not surprising... It + // shouldn't matter but we should find where these bits come from and + // support them. The linux strings are below for completeness. + //X86FeatureMMXEXT + //X86FeatureMP + //X86FeatureFXSR_OPT + //X86Feature3DNOWEXT + //X86Feature3DNOW + //X86FeatureMMXEXT: "mmxext", + //X86FeatureMP: "mp", + //X86FeatureFXSR_OPT: "fxsr_opt", + //X86Feature3DNOWEXT: "3dnowext", + //X86Feature3DNOW: "3dnow", +) + +// linuxBlockOrder defines the order in which linux organizes the feature +// blocks. Linux also tracks feature bits in 32-bit blocks, but in an order +// which doesn't match well here, so for the /proc/cpuinfo generation we simply +// re-map the blocks to Linux's ordering and then go through the bits in each +// block. +var linuxBlockOrder = []block{1, 6, 0, 5, 2, 4} + +// To make emulation of /proc/cpuinfo easy down the line, these names match the +// names of the basic features in Linux defined in +// arch/x86/kernel/cpu/capflags.c. +var x86FeatureStrings = map[Feature]string{ + X86FeatureFPU: "fpu", + X86FeatureVME: "vme", + X86FeatureDE: "de", + X86FeaturePSE: "pse", + X86FeatureTSC: "tsc", + X86FeatureMSR: "msr", + X86FeaturePAE: "pae", + X86FeatureMCE: "mce", + X86FeatureCX8: "cx8", + X86FeatureAPIC: "apic", + X86FeatureSEP: "sep", + X86FeatureMTRR: "mtrr", + X86FeaturePGE: "pge", + X86FeatureMCA: "mca", + X86FeatureCMOV: "cmov", + X86FeaturePAT: "pat", + X86FeaturePSE36: "pse36", + X86FeaturePSN: "pn", + X86FeatureCLFSH: "clflush", + X86FeatureDS: "dts", + X86FeatureACPI: "acpi", + X86FeatureMMX: "mmx", + X86FeatureFXSR: "fxsr", + X86FeatureSSE: "sse", + X86FeatureSSE2: "sse2", + X86FeatureSS: "ss", + X86FeatureHTT: "ht", + X86FeatureTM: "tm", + X86FeatureIA64: "ia64", + X86FeaturePBE: "pbe", + X86FeatureSSE3: "pni", + X86FeaturePCLMULDQ: "pclmulqdq", + X86FeatureDTES64: "dtes64", + X86FeatureMONITOR: "monitor", + X86FeatureDSCPL: "ds_cpl", + X86FeatureVMX: "vmx", + X86FeatureSMX: "smx", + X86FeatureEST: "est", + X86FeatureTM2: "tm2", + X86FeatureSSSE3: "ssse3", + X86FeatureCNXTID: "cid", + X86FeatureFMA: "fma", + X86FeatureCX16: "cx16", + X86FeatureXTPR: "xtpr", + X86FeaturePDCM: "pdcm", + X86FeaturePCID: "pcid", + X86FeatureDCA: "dca", + X86FeatureSSE4_1: "sse4_1", + X86FeatureSSE4_2: "sse4_2", + X86FeatureX2APIC: "x2apic", + X86FeatureMOVBE: "movbe", + X86FeaturePOPCNT: "popcnt", + X86FeatureTSCD: "tsc_deadline_timer", + X86FeatureAES: "aes", + X86FeatureXSAVE: "xsave", + X86FeatureAVX: "avx", + X86FeatureF16C: "f16c", + X86FeatureRDRAND: "rdrand", + X86FeatureFSGSBase: "fsgsbase", + X86FeatureTSC_ADJUST: "tsc_adjust", + X86FeatureBMI1: "bmi1", + X86FeatureHLE: "hle", + X86FeatureAVX2: "avx2", + X86FeatureSMEP: "smep", + X86FeatureBMI2: "bmi2", + X86FeatureERMS: "erms", + X86FeatureINVPCID: "invpcid", + X86FeatureRTM: "rtm", + X86FeatureCQM: "cqm", + X86FeatureMPX: "mpx", + X86FeatureRDT: "rdt", + X86FeatureAVX512F: "avx512f", + X86FeatureAVX512DQ: "avx512dq", + X86FeatureRDSEED: "rdseed", + X86FeatureADX: "adx", + X86FeatureSMAP: "smap", + X86FeatureCLWB: "clwb", + X86FeatureAVX512CD: "avx512cd", + X86FeatureAVX512BW: "avx512bw", + X86FeatureAVX512VL: "avx512vl", + X86FeatureSYSCALL: "syscall", + X86FeatureNX: "nx", + X86FeatureGBPAGES: "pdpe1gb", + X86FeatureRDTSCP: "rdtscp", + X86FeatureLM: "lm", + X86FeatureXSAVEOPT: "xsaveopt", + X86FeatureXSAVEC: "xsavec", + X86FeatureXGETBV1: "xgetbv1", + X86FeatureLAHF64: "lahf_lm", // LAHF/SAHF in long mode + X86FeatureLZCNT: "abm", // Advanced bit manipulation + X86FeaturePREFETCHW: "3dnowprefetch", +} + +// These flags are parse only---they can be used for setting / unsetting the +// flags, but will not get printed out in /proc/cpuinfo. +var x86FeatureParseOnlyStrings = map[Feature]string{ + X86FeaturePKU: "pku", + X86FeatureXSAVES: "xsaves", + X86FeatureFPCSDS: "fpcsds", + X86FeatureOSXSAVE: "osxsave", + X86FeatureIPT: "pt", + X86FeatureSDBG: "sdbg", + X86FeatureFDP_EXCPTN_ONLY: "fdp_excptn_only", + X86FeatureCLFLUSHOPT: "clfushopt", +} + +// These are the default values of various FeatureSet fields. +const ( + defaultVendorID = "GenuineIntel" + + // These processor signature defaults are derived from the values + // listed in Intel AN485 for i7/Xeon processors. + defaultExtFamily uint8 = 0 + defaultExtModel uint8 = 1 + defaultType uint8 = 0 + defaultFamily uint8 = 0x06 + defaultModel uint8 = 0x0a + defaultSteppingID uint8 = 0 +) + +// Just a way to wrap cpuid function numbers. +type cpuidFunction uint32 + +// The constants below are the lower or "standard" cpuid functions. See Intel +// AN485 for detailed information about each one. +const ( + vendorID cpuidFunction = iota // Returns vendor ID and largest standard function. + featureInfo // Returns basic feature bits and processor signature. + cacheDescriptors // Returns list of cache descriptors. + serialNumber // Returns processor serial number (obsolete on new hardware). + deterministicCacheParams // Returns deterministic cache information. See AN485. + monitorMwaitParams // Returns information about monitor/mwait instructions. + powerParams // Returns information about power management and thermal sensors. + extendedFeatureInfo // Returns extended feature bits. + _ // Function 8 is reserved. + DCAParams // Returns direct cache access information. + pmcInfo // Returns information about performance monitoring features. + x2APICInfo // Returns core/logical processor topology. See AN485 for details. + _ // Function 0xc is reserved. + xSaveInfo // Returns information about extended state management. +) + +// The "extended" functions start at 0x80000000. Intel AP-485 has information +// on these as well. +const ( + extendedFunctionInfo cpuidFunction = 0x80000000 + iota // Returns highest available extended function in eax. + extendedFeatures // Returns some extended feature bits in edx and ecx. +) + +var cpuFreqMHz float64 + +// x86FeaturesFromString includes features from x86FeatureStrings and +// x86FeatureParseOnlyStrings. +var x86FeaturesFromString = 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 := x86FeaturesFromString[s] + return f, b +} + +// String implements fmt.Stringer. +func (f Feature) String() string { + if s := f.flagString(false); s != "" { + return s + } + return fmt.Sprintf("<cpuflag %d>", f) +} + +func (f Feature) flagString(cpuinfoOnly bool) string { + if s, ok := x86FeatureStrings[f]; ok { + return s + } + if !cpuinfoOnly { + return x86FeatureParseOnlyStrings[f] + } + return "" +} + +// FeatureSet is a set of Features for a cpu. +type FeatureSet struct { + // Set is the set of features that are enabled in this FeatureSet. + Set map[Feature]bool + + // VendorID is the 12-char string returned in ebx:edx:ecx for eax=0. + VendorID string + + // ExtendedFamily is part of the processor signature. + ExtendedFamily uint8 + + // ExtendedModel is part of the processor signature. + ExtendedModel uint8 + + // ProcessorType is part of the processor signature. + ProcessorType uint8 + + // Family is part of the processor signature. + Family uint8 + + // Model is part of the processor signature. + Model uint8 + + // SteppingID is part of the processor signature. + SteppingID uint8 +} + +// FlagsString prints out supported CPU flags. If cpuinfoOnly is true, it is +// equivalent to the "flags" field in /proc/cpuinfo. +func (fs *FeatureSet) FlagsString(cpuinfoOnly bool) string { + var s []string + for _, b := range linuxBlockOrder { + for i := 0; i < blockSize; i++ { + if f := featureID(b, i); fs.Set[f] { + if fstr := f.flagString(cpuinfoOnly); fstr != "" { + s = append(s, fstr) + } + } + } + } + return strings.Join(s, " ") +} + +// CPUInfo 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) CPUInfo(cpu uint) string { + var b bytes.Buffer + fmt.Fprintf(&b, "processor\t: %d\n", cpu) + fmt.Fprintf(&b, "vendor_id\t: %s\n", fs.VendorID) + fmt.Fprintf(&b, "cpu family\t: %d\n", ((fs.ExtendedFamily<<4)&0xff)|fs.Family) + fmt.Fprintf(&b, "model\t\t: %d\n", ((fs.ExtendedModel<<4)&0xff)|fs.Model) + fmt.Fprintf(&b, "model name\t: %s\n", "unknown") // Unknown for now. + fmt.Fprintf(&b, "stepping\t: %s\n", "unknown") // Unknown for now. + fmt.Fprintf(&b, "cpu MHz\t\t: %.3f\n", cpuFreqMHz) + fmt.Fprintln(&b, "fpu\t\t: yes") + fmt.Fprintln(&b, "fpu_exception\t: yes") + fmt.Fprintf(&b, "cpuid level\t: %d\n", uint32(xSaveInfo)) // Same as ax in vendorID. + fmt.Fprintln(&b, "wp\t\t: yes") + fmt.Fprintf(&b, "flags\t\t: %s\n", fs.FlagsString(true)) + fmt.Fprintf(&b, "bogomips\t: %.02f\n", cpuFreqMHz) // It's bogus anyway. + fmt.Fprintf(&b, "clflush size\t: %d\n", 64) + fmt.Fprintf(&b, "cache_alignment\t: %d\n", 64) + fmt.Fprintf(&b, "address sizes\t: %d bits physical, %d bits virtual\n", 46, 48) + fmt.Fprintln(&b, "power management:") // This is always here, but can be blank. + fmt.Fprintln(&b, "") // The /proc/cpuinfo file ends with an extra newline. + return b.String() +} + +// Helper to convert 3 regs into 12-byte vendor ID. +func vendorIDFromRegs(bx, cx, dx uint32) string { + bytes := make([]byte, 0, 12) + for i := uint(0); i < 4; i++ { + b := byte(bx >> (i * 8)) + bytes = append(bytes, b) + } + + for i := uint(0); i < 4; i++ { + b := byte(dx >> (i * 8)) + bytes = append(bytes, b) + } + + for i := uint(0); i < 4; i++ { + b := byte(cx >> (i * 8)) + bytes = append(bytes, b) + } + return string(bytes) +} + +// 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 optimiazation 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). +func (fs *FeatureSet) ExtendedStateSize() (size, align uint) { + if fs.UseXsave() { + // 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. + _, _, maxSize, _ := HostID(uint32(xSaveInfo), 0) + return uint(maxSize), 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 bits that may be set to 1 in control register +// XCR0. +func (fs *FeatureSet) ValidXCR0Mask() uint64 { + if !fs.UseXsave() { + return 0 + } + eax, _, _, edx := HostID(uint32(xSaveInfo), 0) + return uint64(edx)<<32 | uint64(eax) +} + +// vendorIDRegs returns the 3 register values used to construct the 12-byte +// vendor ID string for eax=0. +func (fs *FeatureSet) vendorIDRegs() (bx, dx, cx uint32) { + for i := uint(0); i < 4; i++ { + bx |= uint32(fs.VendorID[i]) << (i * 8) + } + + for i := uint(0); i < 4; i++ { + dx |= uint32(fs.VendorID[i+4]) << (i * 8) + } + + for i := uint(0); i < 4; i++ { + cx |= uint32(fs.VendorID[i+8]) << (i * 8) + } + return +} + +// signature returns the signature dword that's returned in eax when eax=1. +func (fs *FeatureSet) signature() uint32 { + var s uint32 + s |= uint32(fs.SteppingID & 0xf) + s |= uint32(fs.Model&0xf) << 4 + s |= uint32(fs.Family&0xf) << 8 + s |= uint32(fs.ProcessorType&0x3) << 12 + s |= uint32(fs.ExtendedModel&0xf) << 16 + s |= uint32(fs.ExtendedFamily&0xff) << 20 + return s +} + +// Helper to deconstruct signature dword. +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 +} + +// This factory function is only needed inside the package, package users +// should not be creating and using empty feature sets. +func newEmptyFeatureSet() *FeatureSet { + return newFeatureSet(make(map[Feature]bool)) +} + +// newFeatureSet creates a new FeatureSet with sensible default values and the +// provided set of features. +func newFeatureSet(s map[Feature]bool) *FeatureSet { + return &FeatureSet{ + Set: s, + VendorID: defaultVendorID, + ExtendedFamily: defaultExtFamily, + ExtendedModel: defaultExtModel, + ProcessorType: defaultType, + Family: defaultFamily, + Model: defaultModel, + SteppingID: defaultSteppingID, + } +} + +// Helper to convert blockwise feature bit masks into a set of features. Masks +// must be provided in order for each block, without skipping them. If a block +// does not matter for this feature set, 0 is specified. +func setFromBlockMasks(blocks ...uint32) map[Feature]bool { + s := make(map[Feature]bool) + for b, blockMask := range blocks { + for i := 0; i < blockSize; i++ { + if blockMask&1 != 0 { + s[featureID(block(b), i)] = true + } + blockMask >>= 1 + } + } + return s +} + +// blockMask returns the 32-bit mask associated with a block of features. +func (fs *FeatureSet) blockMask(b block) uint32 { + var mask uint32 + for i := 0; i < blockSize; i++ { + if fs.Set[featureID(b, i)] { + mask |= 1 << uint(i) + } + } + return mask +} + +// Remove removes a Feature from a FeatureSet. It ignores features +// that are not in the FeatureSet. +func (fs *FeatureSet) Remove(feature Feature) { + delete(fs.Set, feature) +} + +// Add adds a Feature to a FeatureSet. It ignores duplicate features. +func (fs *FeatureSet) Add(feature Feature) { + fs.Set[feature] = true +} + +// HasFeature tests whether or not a feature is in the given feature set. +func (fs *FeatureSet) HasFeature(feature Feature) bool { + return fs.Set[feature] +} + +// IsSubset returns true if the FeatureSet is a subset of the FeatureSet passed in. +// This is useful if you want to see if a FeatureSet is compatible with another +// FeatureSet, since you can only run with a given FeatureSet if it's a subset of +// the host's. +func (fs *FeatureSet) IsSubset(other *FeatureSet) bool { + return fs.Subtract(other) == nil +} + +// 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. +func (fs *FeatureSet) Subtract(other *FeatureSet) (diff map[Feature]bool) { + for f := range fs.Set { + if !other.Set[f] { + if diff == nil { + diff = make(map[Feature]bool) + } + diff[f] = true + } + } + + return +} + +// TakeFeatureIntersection will set the features in `fs` to the intersection of +// the features in `fs` and `other` (effectively clearing any feature bits on +// `fs` that are not also set in `other`). +func (fs *FeatureSet) TakeFeatureIntersection(other *FeatureSet) { + for f := range fs.Set { + if !other.Set[f] { + delete(fs.Set, f) + } + } +} + +// EmulateID emulates a cpuid instruction based on the feature set. +func (fs *FeatureSet) EmulateID(origAx, origCx uint32) (ax, bx, cx, dx uint32) { + switch cpuidFunction(origAx) { + case vendorID: + ax = uint32(xSaveInfo) // 0xd (xSaveInfo) is the highest function we support. + bx, dx, cx = fs.vendorIDRegs() + case featureInfo: + // clflush line size (ebx bits[15:8]) hardcoded as 8. This + // means cache lines of size 64 bytes. + bx = 8 << 8 + cx = fs.blockMask(block(0)) + dx = fs.blockMask(block(1)) + ax = fs.signature() + case xSaveInfo: + if !fs.UseXsave() { + return 0, 0, 0, 0 + } + return HostID(uint32(xSaveInfo), origCx) + case extendedFeatureInfo: + if origCx != 0 { + break // Only leaf 0 is supported. + } + bx = fs.blockMask(block(2)) + cx = fs.blockMask(block(3)) + case extendedFunctionInfo: + // We only support showing the extended features. + ax = uint32(extendedFeatures) + cx = 0 + case extendedFeatures: + cx = fs.blockMask(block(5)) + dx = fs.blockMask(block(6)) + } + + return +} + +// UseXsave returns the choice of fp state saving instruction. +func (fs *FeatureSet) UseXsave() bool { + return fs.HasFeature(X86FeatureXSAVE) && fs.HasFeature(X86FeatureOSXSAVE) +} + +// UseXsaveopt returns true if 'fs' supports the "xsaveopt" instruction. +func (fs *FeatureSet) UseXsaveopt() bool { + return fs.UseXsave() && fs.HasFeature(X86FeatureXSAVEOPT) +} + +// HostID executes a native CPUID instruction. +func HostID(axArg, cxArg uint32) (ax, bx, cx, dx uint32) + +// HostFeatureSet uses cpuid to get host values and construct a feature set +// that matches that of the host machine. Note that there are several places +// where there appear to be some unecessary assignments between register names +// (ax, bx, cx, or dx) and featureBlockN variables. This is to explicitly show +// where the different feature blocks come from, to make the code easier to +// inspect and read. +func HostFeatureSet() *FeatureSet { + // eax=0 gets max supported feature and vendor ID. + _, bx, cx, dx := HostID(0, 0) + vendorID := vendorIDFromRegs(bx, cx, dx) + + // eax=1 gets basic features in ecx:edx. + ax, _, cx, dx := HostID(1, 0) + featureBlock0 := cx + featureBlock1 := dx + ef, em, pt, f, m, sid := signatureSplit(ax) + + // eax=7, ecx=0 gets extended features in ecx:ebx. + _, bx, cx, _ = HostID(7, 0) + featureBlock2 := bx + featureBlock3 := cx + + // Leaf 0xd is supported only if CPUID.1:ECX.XSAVE[bit 26] is set. + var featureBlock4 uint32 + if (featureBlock0 & (1 << 26)) != 0 { + featureBlock4, _, _, _ = HostID(uint32(xSaveInfo), 1) + } + + // eax=0x80000000 gets supported extended levels. We use this to + // determine if there are any non-zero block 4 or block 6 bits to find. + var featureBlock5, featureBlock6 uint32 + if ax, _, _, _ := HostID(uint32(extendedFunctionInfo), 0); ax >= uint32(extendedFeatures) { + // eax=0x80000001 gets AMD added feature bits. + _, _, cx, dx = HostID(uint32(extendedFeatures), 0) + featureBlock5 = cx + featureBlock6 = dx + } + + set := setFromBlockMasks(featureBlock0, featureBlock1, featureBlock2, featureBlock3, featureBlock4, featureBlock5, featureBlock6) + return &FeatureSet{ + Set: set, + VendorID: vendorID, + ExtendedFamily: ef, + ExtendedModel: em, + ProcessorType: pt, + Family: f, + Model: m, + SteppingID: sid, + } +} + +// Reads max cpu frequency from host /proc/cpuinfo. Must run before +// whitelisting. This value is used to create the fake /proc/cpuinfo from a +// FeatureSet. +func initCPUFreq() { + cpuinfob, err := ioutil.ReadFile("/proc/cpuinfo") + if err != nil { + // Leave it as 0... The standalone 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 innacurate. 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") +} + +func initFeaturesFromString() { + for f, s := range x86FeatureStrings { + x86FeaturesFromString[s] = f + } + for f, s := range x86FeatureParseOnlyStrings { + x86FeaturesFromString[s] = f + } +} + +func init() { + // initCpuFreq must be run before whitelists are enabled. + initCPUFreq() + initFeaturesFromString() +} diff --git a/pkg/cpuid/cpuid_parse_test.go b/pkg/cpuid/cpuid_parse_test.go new file mode 100644 index 000000000..c4f52818c --- /dev/null +++ b/pkg/cpuid/cpuid_parse_test.go @@ -0,0 +1,38 @@ +// Copyright 2018 Google Inc. +// +// 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 ( + "io/ioutil" + "strings" + "testing" +) + +// TestHostFeatureFlags ensures that package cpuid recognizes all features +// present on this host. +func TestHostFeatureFlags(t *testing.T) { + cpuinfoBytes, _ := ioutil.ReadFile("/proc/cpuinfo") + cpuinfo := string(cpuinfoBytes) + t.Logf("Host cpu info:\n%s", cpuinfo) + + for f := range HostFeatureSet().Set { + if f.flagString(false) == "" { + t.Errorf("Non-parsable feature: %v", f) + } + if s := f.flagString(true); !strings.Contains(cpuinfo, s) { + t.Errorf("Non-native flag: %v", f) + } + } +} diff --git a/pkg/cpuid/cpuid_test.go b/pkg/cpuid/cpuid_test.go new file mode 100644 index 000000000..02f732f85 --- /dev/null +++ b/pkg/cpuid/cpuid_test.go @@ -0,0 +1,223 @@ +// Copyright 2018 Google Inc. +// +// 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" +) + +var justFPU = &FeatureSet{ + Set: map[Feature]bool{ + X86FeatureFPU: true, + }} + +var justFPUandPAE = &FeatureSet{ + Set: map[Feature]bool{ + X86FeatureFPU: true, + X86FeaturePAE: true, + }} + +func TestIsSubset(t *testing.T) { + if !justFPU.IsSubset(justFPUandPAE) { + t.Errorf("Got %v is not subset of %v, want IsSubset being true", justFPU, justFPUandPAE) + } + + if justFPUandPAE.IsSubset(justFPU) { + t.Errorf("Got %v is a subset of %v, want IsSubset being false", justFPU, justFPUandPAE) + } +} + +func TestTakeFeatureIntersection(t *testing.T) { + testFeatures := HostFeatureSet() + testFeatures.TakeFeatureIntersection(justFPU) + if !testFeatures.IsSubset(justFPU) { + t.Errorf("Got more features than expected after intersecting host features with justFPU: %v, want %v", testFeatures.Set, justFPU.Set) + } + if !testFeatures.HasFeature(X86FeatureFPU) { + t.Errorf("Got no features in testFeatures after intersecting, want %v", X86FeatureFPU) + } +} + +// TODO: 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.IsSubset(hostFeatures) { + 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) + + 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)) + } +} |