// 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 ( "context" "fmt" "io" ) // FeatureSet defines features in terms of CPUID leaves and bits. // The kernel also exposes the presence of features to userspace through // a set of flags(HWCAP/HWCAP2) bits, exposed in the auxiliary vector, which // are necessary to read for some features (e.g. FSGSBASE). // // 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)"` // hwCap stores HWCAP1/2 exposed from the elf auxiliary vector. hwCap hwCap } // 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(_ context.Context, 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. Several fields are simply made up. func (fs FeatureSet) WriteCPUInfoTo(cpu, numCPU uint, w io.Writer) { // Avoid many redundant 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) // Pretend the CPU has 8192 KB of cache. Note that real /proc/cpuinfo exposes total L3 cache // size on Intel and per-core L2 cache size on AMD (as of Linux 6.1.0), so the value of this // field is not really important in practice. Any value that is chosen here will be wrong // by an order of magnitude on a significant chunk of x86 machines. // 8192 KB is selected because it is a reasonable size that will be effectively usable on // lightly loaded machines - most machines have 1-4MB of L3 cache per core. fmt.Fprintf(w, "cache size\t: 8192 KB\n") fmt.Fprintf(w, "physical id\t: 0\n") // Pretend all CPUs are in the same socket. fmt.Fprintf(w, "siblings\t: %d\n", numCPU) fmt.Fprintf(w, "core id\t\t: %d\n", cpu) fmt.Fprintf(w, "cpu cores\t: %d\n", numCPU) // Pretend each CPU is a distinct core (rather than a hyperthread). fmt.Fprintf(w, "apicid\t\t: %d\n", cpu) fmt.Fprintf(w, "initial apicid\t: %d\n", cpu) 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 ( xsaveSize = native(In{Eax: uint32(xSaveInfo)}).Ebx maxXsaveSize = native(In{Eax: uint32(xSaveInfo)}).Ecx amxTileCfgSize = native(In{Eax: uint32(xSaveInfo), Ecx: 17}).Eax amxTileDataSize = native(In{Eax: uint32(xSaveInfo), Ecx: 18}).Eax ) const ( // XCR0AMXMask are the bits that enable xsave to operate on AMX TILECFG // and TILEDATA. // // Note: TILECFG and TILEDATA are always either both enabled or both // disabled. // // See Intel® 64 and IA-32 Architectures Software Developer’s Manual Vol.1 // section 13.3 for details. XCR0AMXMask = uint64((1 << 17) | (1 << 18)) ) // ExtendedStateSize returns the number of bytes needed to save the "extended // state" for the enabled features 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: the return value matches the size of signal FP state frames. // Look at check_xstate_in_sigframe() in the kernel sources for more details. // //go:nosplit func (fs FeatureSet) ExtendedStateSize() (size, align uint) { if fs.UseXsave() { return uint(xsaveSize), 64 } // If we don't support xsave, we fall back to fxsave, which requires // 512 bytes aligned to 16 bytes. return 512, 16 } // AMXExtendedStateSize returns the number of bytes within the "extended state" // area that is used for AMX. func (fs FeatureSet) AMXExtendedStateSize() uint { if fs.UseXsave() { xcr0 := xgetbv(0) if (xcr0 & XCR0AMXMask) != 0 { return uint(amxTileCfgSize + amxTileDataSize) } } return 0 } // ValidXCR0Mask returns the valid bits in control register XCR0. // // Always exclude AMX bits, because we do not support it. // TODO(gvisor.dev/issues/9896): Implement AMX Support. // //go:nosplit func (fs FeatureSet) ValidXCR0Mask() uint64 { if !fs.HasFeature(X86FeatureXSAVE) { return 0 } ax, _, _, dx := fs.query(xSaveInfo) return (uint64(dx)<<32 | uint64(ax)) &^ XCR0AMXMask } // 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) } // UseXsavec returns true if 'fs' supports the "xsavec" instruction. // //go:nosplit func (fs FeatureSet) UseXsavec() bool { return fs.UseXsaveopt() && fs.HasFeature(X86FeatureXSAVEC) } // UseFSGSBASE returns true if 'fs' supports the (RD|WR)(FS|GS)BASE instructions. func (fs FeatureSet) UseFSGSBASE() bool { HWCAP2_FSGSBASE := uint64(1) << 1 return fs.HasFeature(X86FeatureFSGSBase) && ((fs.hwCap.hwCap2 & HWCAP2_FSGSBASE) != 0) } // 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 } // AllowedHWCap1 returns the HWCAP1 bits that the guest is allowed to depend // on. func (fs FeatureSet) AllowedHWCap1() uint64 { // HWCAPS are not supported on amd64. return 0 } // AllowedHWCap2 returns the HWCAP2 bits that the guest is allowed to depend // on. func (fs FeatureSet) AllowedHWCap2() uint64 { // HWCAPS are not supported on amd64. return 0 }