[zircon] Add CPUID library.

Noncomprehensive CPUID library, more than the kernel's current cpuid
parsing but less than complete. It has enough to eventually replace our
current cpuid parsing. It also has logic to parse topology information,
and unit tests.

Issue: ZX-3068

Test: Added unit tests, ran with "k ut cpuid" on qemu.
Change-Id: I64309d75f8e276d4a608aee5456c587172e28ddb
diff --git a/kernel/arch/x86/cpuid/cpuid.cpp b/kernel/arch/x86/cpuid/cpuid.cpp
new file mode 100644
index 0000000..c66c4c0
--- /dev/null
+++ b/kernel/arch/x86/cpuid/cpuid.cpp
@@ -0,0 +1,225 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <arch/x86/cpuid.h>
+
+#include <memory>
+
+namespace cpu_id {
+namespace {
+
+inline uint8_t BaseFamilyFromEax(uint64_t eax) {
+    return (eax & 0xF00) >> 8;
+}
+
+Registers CallCpuId(uint32_t leaf, uint32_t subleaf = 0) {
+    Registers result;
+    // Set EAX and ECX to the initial values, call cpuid and copy results into
+    // the result object.
+    asm volatile(
+        "cpuid"
+        : "=a"(result.reg[Registers::EAX]),
+          "=b"(result.reg[Registers::EBX]),
+          "=c"(result.reg[Registers::ECX]),
+          "=d"(result.reg[Registers::EDX])
+        : "a"(leaf), "c"(subleaf));
+    return result;
+}
+
+// Convert power of two value to a shift_width.
+uint8_t ToShiftWidth(size_t value) {
+    if (value == 0) {
+        return 0;
+    }
+
+    uint8_t count = 0;
+    while (!((value >> count++) & 1))
+        ;
+    return --count; // an extra increment was added before testing.
+}
+
+} // namespace
+
+ManufacturerInfo CpuId::ReadManufacturerInfo() const {
+    return ManufacturerInfo(CallCpuId(0));
+}
+
+ProcessorId CpuId::ReadProcessorId() const {
+    return ProcessorId(CallCpuId(1));
+}
+
+Features CpuId::ReadFeatures() const {
+    return Features(CallCpuId(1), CallCpuId(7), CallCpuId(0x80000001));
+}
+
+Topology CpuId::ReadTopology() const {
+    Registers leafB[] = {
+        CallCpuId(11, 0),
+        CallCpuId(11, 1),
+        CallCpuId(11, 2)};
+    return Topology(ReadManufacturerInfo(), ReadFeatures(), CallCpuId(4), leafB);
+}
+
+ManufacturerInfo::ManufacturerInfo(Registers registers)
+    : registers_(registers) {}
+
+ManufacturerInfo::Manufacturer ManufacturerInfo::manufacturer() const {
+    char buffer[kManufacturerIdLength + 1] = {0};
+    manufacturer_id(buffer);
+    if (strcmp("GenuineIntel", buffer) == 0) {
+        return INTEL;
+    } else if (strcmp("AuthenticAMD", buffer) == 0) {
+        return AMD;
+    } else {
+        return OTHER;
+    }
+}
+
+void ManufacturerInfo::manufacturer_id(char* buffer) const {
+    union {
+        uint32_t regs[3];
+        char string[13];
+    } translator = {.regs = {registers_.ebx(), registers_.edx(), registers_.ecx()}};
+
+    memcpy(buffer, translator.string, kManufacturerIdLength);
+}
+
+size_t ManufacturerInfo::highest_cpuid_leaf() const {
+    return registers_.eax();
+}
+
+ProcessorId::ProcessorId(Registers registers)
+    : registers_(registers) {}
+
+uint8_t ProcessorId::stepping() const {
+    return registers_.eax() & 0xF;
+}
+
+uint16_t ProcessorId::model() const {
+    const uint8_t base = (registers_.eax() >> 4) & 0xF;
+    const uint8_t extended = (registers_.eax() >> 16) & 0xF;
+
+    const uint8_t family = BaseFamilyFromEax(registers_.eax());
+    if (family == 0xF || family == 0x6) {
+        return static_cast<uint16_t>((extended << 4) + base);
+    } else {
+        return base;
+    }
+}
+
+uint16_t ProcessorId::family() const {
+    const uint8_t base = BaseFamilyFromEax(registers_.eax());
+    const uint8_t extended = (registers_.eax() >> 20) & 0xFF;
+    if (base == 0xF) {
+        return static_cast<uint16_t>(base + extended);
+    } else {
+        return base;
+    }
+}
+
+uint8_t ProcessorId::local_apic_id() const {
+    return static_cast<uint8_t>((registers_.ebx() >> 24) & 0xFF);
+}
+
+Features::Features(Registers leaf1, Registers leaf7, Registers leaf8_01)
+    : leaves_{leaf1, leaf7, leaf8_01} {}
+
+uint8_t Features::max_logical_processors_in_package() const {
+    return (leaves_[LEAF1].ebx() >> 16) & 0x7F;
+}
+
+Topology::Topology(ManufacturerInfo info, Features features, Registers leaf4, Registers* leafB)
+    : info_(info), features_(features), leaf4_(leaf4), leafB_{leafB[0], leafB[1], leafB[2]} {}
+
+std::optional<Topology::Levels> Topology::IntelLevels() const {
+    Topology::Levels levels;
+    if (info_.highest_cpuid_leaf() >= 11) {
+        int nodes_under_previous_level = 0;
+        int bits_in_previous_levels = 0;
+        for (int i = 0; i < 3; i++) {
+            const uint8_t width = leafB_[i].eax() & 0xF;
+            if (width) {
+                uint8_t raw_type = (leafB_[i].ecx() & 0xFF00) >> 8;
+
+                LevelType type = LevelType::INVALID;
+                if (raw_type == 1) {
+                    type = LevelType::SMT;
+                } else if (raw_type == 2) {
+                    type = LevelType::CORE;
+                } else if (i == 2) {
+                    // Package is defined as the "last" level.
+                    type = LevelType::PACKAGE;
+                }
+
+                // This actually contains total logical processors in all
+                // subtrees of this level of the topology.
+                const uint16_t nodes_under_level = leafB_[i].ebx() & 0xFF;
+                const uint8_t node_count = static_cast<uint8_t>(
+                    nodes_under_level / ((nodes_under_previous_level == 0) ? 1 : nodes_under_previous_level));
+                const uint8_t shift_width = static_cast<uint8_t>(width - bits_in_previous_levels);
+
+                levels.levels[levels.level_count++] = {
+                    .type = type,
+                    // Dividing by logical processors under last level will give
+                    // us the nodes that are at this level.
+                    .node_count = node_count,
+                    .shift_width = shift_width,
+                };
+                nodes_under_previous_level += nodes_under_level;
+                bits_in_previous_levels += shift_width;
+            }
+        }
+    } else if (info_.highest_cpuid_leaf() >= 4) {
+        const bool single_core = !features_.HasFeature(Features::HTT);
+        if (single_core) {
+            levels.levels[levels.level_count++] = {
+                .type = LevelType::PACKAGE,
+                .node_count = 1,
+                .shift_width = 0,
+            };
+        } else {
+            const auto logical_in_package = features_.max_logical_processors_in_package();
+            const auto cores_in_package = ((leaf4_.eax() >> 26) & 0x3F) + 1;
+            const auto logical_per_core = logical_in_package / cores_in_package;
+            if (logical_per_core > 1) {
+                levels.levels[levels.level_count++] = {
+                    .type = LevelType::SMT,
+                    .shift_width = ToShiftWidth(logical_per_core),
+                };
+            }
+
+            if (cores_in_package > 1) {
+                levels.levels[levels.level_count++] = {
+                    .type = LevelType::CORE,
+                    .shift_width = ToShiftWidth(cores_in_package),
+                };
+            }
+        }
+    } else {
+        // If this is an intel CPU then cpuid leaves are disabled on the system
+        // IA32_MISC_ENABLES[22] == 1. This can be set to 0, usually in the BIOS,
+        // the kernel can change it too but we prefer to stay read-only here.
+        return std::nullopt;
+    }
+
+    return {levels};
+}
+
+std::optional<Topology::Levels> Topology::levels() const {
+    auto levels = IntelLevels();
+    if (levels) {
+        return levels;
+    }
+
+    // If Intel approach didn't work try the AMD approach, even on AMD chips the
+    // intel approach may work, there are hypervisor cases that populate it.
+    // TODO(edcoyne): Implement AMD approach.
+    printf("WARNING: AMD processors are not yet supported. \n");
+
+    printf("WARNING: Unable to parse topology from CPUID. If this is an Intel chip, "
+           "ensure IA32_MISC_ENABLES[22] is off.\n");
+    return std::nullopt;
+}
+
+} // namespace cpu_id
diff --git a/kernel/arch/x86/cpuid/cpuid_test.cpp b/kernel/arch/x86/cpuid/cpuid_test.cpp
new file mode 100644
index 0000000..77f4528
--- /dev/null
+++ b/kernel/arch/x86/cpuid/cpuid_test.cpp
@@ -0,0 +1,199 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <arch/x86/cpuid.h>
+
+#include <initializer_list>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <lib/unittest/unittest.h>
+
+namespace {
+using cpu_id::CpuId;
+using cpu_id::Features;
+using cpu_id::ManufacturerInfo;
+using cpu_id::ProcessorId;
+using cpu_id::Registers;
+using cpu_id::Topology;
+
+} // namespace
+
+struct TestDataSet {
+    Features::Feature features[200];
+    Features::Feature missing_features[200];
+    Registers leaf0;
+    Registers leaf1;
+    Registers leaf4;
+    Registers leaf7;
+    Registers leafB[3];
+    Registers leaf80000001;
+};
+
+// Queried from a Intel Xeon E5-2690v4.
+const TestDataSet Xeon2690v4Data = {
+    .features = {Features::FPU, Features::VME, Features::DE, Features::PSE, Features::TSC,
+                 Features::MSR, Features::PAE, Features::MCE, Features::CX8, Features::APIC, Features::SEP,
+                 Features::MTRR, Features::PGE, Features::MCA, Features::CMOV, Features::PAT,
+                 Features::PSE36, Features::ACPI, Features::MMX, Features::FSGSBASE,
+                 Features::FXSR, Features::SSE, Features::SSE2, Features::SS, Features::HTT, Features::TM,
+                 Features::PBE, Features::SYSCALL, Features::XD, Features::PDPE1GB, Features::RDTSCP,
+                 Features::PCLMULQDQ, Features::DTES64, Features::MONITOR, Features::DS_CPL, Features::VMX,
+                 Features::SMX, Features::EST, Features::TM2, Features::SSSE3, Features::SDBG,
+                 Features::FMA, Features::CX16, Features::XTPR, Features::PDCM, Features::PCID,
+                 Features::DCA, Features::SSE4_1, Features::SSE4_2, Features::X2APIC, Features::MOVBE,
+                 Features::POPCNT, Features::AES, Features::XSAVE, Features::AVX, Features::F16C,
+                 Features::RDRAND, Features::LAHF, Features::BMI1, Features::HLE, Features::AVX2,
+                 Features::SMEP, Features::BMI2, Features::ERMS, Features::INVPCID, Features::RTM,
+                 Features::RDSEED, Features::ADX, Features::SMAP, Features::INTEL_PT},
+    .missing_features = {Features::PSN, Features::AVX512VNNI},
+    .leaf0 = {.reg = {0x14, 0x756E6547, 0x6C65746E, 0x49656E69}},
+    .leaf1 = {.reg = {0x406F1, 0x12200800, 0x7FFEFBFF, 0xBFEBFBFF}},
+    .leaf4 = {.reg = {0x3C004121, 0x1C0003F, 0x3F, 0x0}},
+    .leaf7 = {.reg = {0x0, 0x21CBFBB, 0x0, 0x9C000000}},
+    .leafB = {{.reg = {0x1, 0x2, 0x100, 0x28}},
+              {.reg = {0x5, 0x1C, 0x201, 0x29}},
+              {.reg = {0x0, 0x0, 0x2, 0x38}}},
+    .leaf80000001 = {.reg = {0x0, 0x0, 0x121, 0x2C100800}},
+};
+
+bool test_feature_flags() {
+    BEGIN_TEST;
+
+    auto data = Xeon2690v4Data;
+    Features features(data.leaf1, data.leaf7, data.leaf80000001);
+
+    // Features we know this processor has.
+    for (auto feature : data.features) {
+        if (feature.leaf == Features::INVALID_SET)
+            continue;
+
+        const bool result = features.HasFeature(feature);
+        if (!result) {
+            printf("Missing Feature: set:%u reg:%u bit:%u\n",
+                   feature.leaf, feature.reg, feature.bit);
+        }
+        EXPECT_TRUE(result, "");
+    }
+
+    // Some features we know it doesn't.
+    for (auto feature : data.missing_features) {
+        if (feature.leaf == Features::INVALID_SET)
+            continue;
+
+        const bool result = features.HasFeature(feature);
+        if (result) {
+            printf("Extra Feature: set:%u reg:%u bit:%u\n",
+                   feature.leaf, feature.reg, feature.bit);
+        }
+        EXPECT_FALSE(result, "");
+    }
+
+    END_TEST;
+}
+
+bool test_max_logical_processors() {
+    BEGIN_TEST;
+
+    auto data = Xeon2690v4Data;
+    Features features(data.leaf1, data.leaf7, data.leaf80000001);
+
+    EXPECT_EQ(32, features.max_logical_processors_in_package(), "");
+
+    END_TEST;
+}
+
+bool test_manufacturer_info() {
+    BEGIN_TEST;
+    auto data = Xeon2690v4Data;
+
+    char buffer[ManufacturerInfo::kManufacturerIdLength + 1] = {0};
+    auto info = ManufacturerInfo(data.leaf0);
+    info.manufacturer_id(buffer);
+
+    EXPECT_TRUE(strcmp("GenuineIntel", buffer) == 0, buffer);
+    EXPECT_EQ(ManufacturerInfo::INTEL, info.manufacturer(), "");
+    EXPECT_EQ(20u, info.highest_cpuid_leaf(), "");
+
+    END_TEST;
+}
+
+bool test_processor_id() {
+    BEGIN_TEST;
+    auto data = Xeon2690v4Data;
+
+    ProcessorId proc(data.leaf1);
+    EXPECT_EQ(6, proc.family(), "");
+    EXPECT_EQ(79, proc.model(), "");
+    EXPECT_EQ(1, proc.stepping(), "");
+
+    END_TEST;
+}
+
+bool test_topology() {
+    BEGIN_TEST;
+    auto data = Xeon2690v4Data;
+
+    Topology topology(ManufacturerInfo(data.leaf0),
+                      Features(data.leaf1, data.leaf7, data.leaf80000001),
+                      data.leaf4, data.leafB);
+
+    const auto levels_opt = topology.levels();
+    ASSERT_TRUE(levels_opt, "");
+
+    const auto& levels = *levels_opt;
+    EXPECT_EQ(Topology::LevelType::SMT, levels.levels[0].type, "");
+    EXPECT_EQ(2u, levels.levels[0].node_count, "");
+    EXPECT_EQ(1u, levels.levels[0].shift_width, "");
+
+    EXPECT_EQ(Topology::LevelType::CORE, levels.levels[1].type, "");
+    EXPECT_EQ(14u, levels.levels[1].node_count, "");
+    EXPECT_EQ(4u, levels.levels[1].shift_width, "");
+
+    EXPECT_EQ(Topology::LevelType::INVALID, levels.levels[2].type, "");
+
+    END_TEST;
+}
+
+// Tests other intel path, using leaf4 instead of extended leafB.
+bool test_topology_intel_leaf4() {
+    BEGIN_TEST;
+    auto data = Xeon2690v4Data;
+
+    // We need to report that we don't support leafB.
+    auto modifiedLeaf0 = data.leaf0;
+    modifiedLeaf0.reg[Registers::EAX] = 4;
+    auto manufacturer = ManufacturerInfo(modifiedLeaf0);
+    EXPECT_EQ(4u, manufacturer.highest_cpuid_leaf(), "");
+
+    Topology topology(manufacturer,
+                      Features(data.leaf1, data.leaf7, data.leaf80000001),
+                      data.leaf4, data.leafB);
+
+    const auto levels_opt = topology.levels();
+    ASSERT_TRUE(levels_opt, "");
+
+    const auto& levels = *levels_opt;
+    EXPECT_EQ(Topology::LevelType::SMT, levels.levels[0].type, "");
+    EXPECT_EQ(Topology::kInvalidCount, levels.levels[0].node_count, "");
+    EXPECT_EQ(1u, levels.levels[0].shift_width, "");
+
+    EXPECT_EQ(Topology::LevelType::CORE, levels.levels[1].type, "");
+    EXPECT_EQ(Topology::kInvalidCount, levels.levels[1].node_count, "");
+    EXPECT_EQ(4u, levels.levels[1].shift_width, "");
+
+    EXPECT_EQ(Topology::LevelType::INVALID, levels.levels[2].type, "");
+
+    END_TEST;
+}
+
+UNITTEST_START_TESTCASE(cpuid_tests)
+UNITTEST("Parse feature flags from static data.", test_feature_flags)
+UNITTEST("Parse maximum logical processors from static data.", test_max_logical_processors)
+UNITTEST("Parse manufacturer info from static data.", test_manufacturer_info)
+UNITTEST("Parse processor id from static data.", test_processor_id)
+UNITTEST("Parse topology from static data.", test_topology)
+UNITTEST("Parse topology from static data, using leaf4.", test_topology_intel_leaf4)
+UNITTEST_END_TESTCASE(cpuid_tests, "cpuid", "Test parsing of cpuid values.");
diff --git a/kernel/arch/x86/cpuid/include/arch/x86/cpuid.h b/kernel/arch/x86/cpuid/include/arch/x86/cpuid.h
new file mode 100644
index 0000000..fd9725b
--- /dev/null
+++ b/kernel/arch/x86/cpuid/include/arch/x86/cpuid.h
@@ -0,0 +1,305 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef KERNEL_ARCH_X86_CPUID_H_
+#define KERNEL_ARCH_X86_CPUID_H_
+
+#include <cstdint>
+#include <cstring>
+#include <optional>
+
+#include <assert.h>
+
+namespace cpu_id {
+
+struct Registers {
+    enum {
+        EAX = 0,
+        EBX = 1,
+        ECX = 2,
+        EDX = 3,
+    };
+
+    inline uint32_t eax() const {
+        return reg[EAX];
+    }
+    inline uint32_t ebx() const {
+        return reg[EBX];
+    }
+    inline uint32_t edx() const {
+        return reg[EDX];
+    }
+    inline uint32_t ecx() const {
+        return reg[ECX];
+    }
+
+    uint32_t reg[4];
+};
+
+// Extracts the manufacturer id string from call with EAX=0.
+class ManufacturerInfo {
+public:
+    enum Manufacturer {
+        INTEL,
+        AMD,
+        OTHER
+    };
+
+    // How many chars are in a manufacturer id.
+    static constexpr size_t kManufacturerIdLength = 12;
+
+    ManufacturerInfo(Registers leaf0);
+
+    Manufacturer manufacturer() const;
+
+    // Reads the manufacturer id and writes it into the buffer, buffer should be
+    // at least kManufacturerIdLength in length. This will not null-terminate the
+    // string.
+    void manufacturer_id(char* buffer) const;
+
+    // Highest leaf (EAX parameter to cpuid) that this processor supports.
+    size_t highest_cpuid_leaf() const;
+
+private:
+    const Registers registers_;
+};
+
+// Extracts the processor signature/id from call with EAX=1.
+class ProcessorId {
+public:
+    ProcessorId(Registers registers);
+
+    // Stepping, or revision, of this model.
+    uint8_t stepping() const;
+
+    // Model inside of the given family.
+    uint16_t model() const;
+
+    // Family of processors to which this chip belongs.
+    uint16_t family() const;
+
+    // APIC ID of the processor on which this object was generated. Note this
+    // class uses a cached copy of registers so if this object was generated on
+    // a differnet processor this value could be misleading.
+    uint8_t local_apic_id() const;
+
+private:
+    const Registers registers_;
+};
+
+// Extracts feature flags from EAX=1 call and extended feature flags calls.
+// See docs for full listing of possible features, this class is not
+// comprehensive, things are added as they are required.
+class Features {
+public:
+    enum LeafIndex {
+        LEAF1,
+        LEAF7,
+        LEAF8_01,
+        INVALID_SET = 254,
+    };
+
+    struct Feature {
+        uint8_t leaf = INVALID_SET;
+        uint8_t reg;
+        uint8_t bit;
+    };
+
+    // Feature "enum".
+    static constexpr Feature FPU = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 0};
+    static constexpr Feature VME = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 1};
+    static constexpr Feature DE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 2};
+    static constexpr Feature PSE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 3};
+    static constexpr Feature TSC = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 4};
+    static constexpr Feature MSR = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 5};
+    static constexpr Feature PAE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 6};
+    static constexpr Feature MCE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 7};
+    static constexpr Feature CX8 = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 8};
+    static constexpr Feature APIC = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 9};
+    static constexpr Feature SEP = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 11};
+    static constexpr Feature MTRR = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 12};
+    static constexpr Feature PGE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 13};
+    static constexpr Feature MCA = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 14};
+    static constexpr Feature CMOV = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 15};
+    static constexpr Feature PAT = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 16};
+    static constexpr Feature PSE36 = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 17};
+    static constexpr Feature PSN = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 18};
+    static constexpr Feature CLFSH = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 19};
+    static constexpr Feature DS = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 21};
+    static constexpr Feature ACPI = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 22};
+    static constexpr Feature MMX = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 23};
+    static constexpr Feature FXSR = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 24};
+    static constexpr Feature SSE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 25};
+    static constexpr Feature SSE2 = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 26};
+    static constexpr Feature SS = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 27};
+    static constexpr Feature HTT = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 28};
+    static constexpr Feature TM = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 29};
+    static constexpr Feature PBE = {.leaf = LEAF1, .reg = Registers::EDX, .bit = 31};
+    static constexpr Feature SSE3 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 0};
+    static constexpr Feature PCLMULQDQ = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 1};
+    static constexpr Feature DTES64 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 2};
+    static constexpr Feature MONITOR = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 3};
+    static constexpr Feature DS_CPL = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 4};
+    static constexpr Feature VMX = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 5};
+    static constexpr Feature SMX = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 6};
+    static constexpr Feature EST = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 7};
+    static constexpr Feature TM2 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 8};
+    static constexpr Feature SSSE3 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 9};
+    static constexpr Feature CNXT_ID = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 10};
+    static constexpr Feature SDBG = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 11};
+    static constexpr Feature FMA = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 12};
+    static constexpr Feature CX16 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 13};
+    static constexpr Feature XTPR = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 14};
+    static constexpr Feature PDCM = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 15};
+    static constexpr Feature PCID = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 17};
+    static constexpr Feature DCA = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 18};
+    static constexpr Feature SSE4_1 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 19};
+    static constexpr Feature SSE4_2 = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 20};
+    static constexpr Feature X2APIC = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 21};
+    static constexpr Feature MOVBE = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 22};
+    static constexpr Feature POPCNT = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 23};
+    static constexpr Feature TSC_DEADLINE = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 24};
+    static constexpr Feature AES = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 25};
+    static constexpr Feature XSAVE = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 26};
+    static constexpr Feature OSXSAVE = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 27};
+    static constexpr Feature AVX = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 28};
+    static constexpr Feature F16C = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 29};
+    static constexpr Feature RDRAND = {.leaf = LEAF1, .reg = Registers::ECX, .bit = 30};
+
+    static constexpr Feature FSGSBASE = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 0};
+    static constexpr Feature SGX = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 2};
+    static constexpr Feature BMI1 = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 3};
+    static constexpr Feature HLE = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 4};
+    static constexpr Feature AVX2 = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 5};
+    static constexpr Feature SMEP = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 7};
+    static constexpr Feature BMI2 = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 8};
+    static constexpr Feature ERMS = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 9};
+    static constexpr Feature INVPCID = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 10};
+    static constexpr Feature RTM = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 11};
+    static constexpr Feature PQM = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 12};
+    static constexpr Feature MPX = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 14};
+    static constexpr Feature PQE = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 15};
+    static constexpr Feature AVX512F = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 16};
+    static constexpr Feature AVX512DQ = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 17};
+    static constexpr Feature RDSEED = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 18};
+    static constexpr Feature ADX = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 19};
+    static constexpr Feature SMAP = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 20};
+    static constexpr Feature AVX512IFMA = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 21};
+    static constexpr Feature PCOMMIT = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 22};
+    static constexpr Feature CLWB = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 24};
+    static constexpr Feature INTEL_PT = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 25};
+    static constexpr Feature AVX512PF = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 26};
+    static constexpr Feature AVX512ER = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 27};
+    static constexpr Feature AVX512CD = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 28};
+    static constexpr Feature SHA = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 29};
+    static constexpr Feature AVX512BW = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 30};
+    static constexpr Feature AVX512VL = {.leaf = LEAF7, .reg = Registers::EBX, .bit = 31};
+    static constexpr Feature PREFETCHWT1 = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 0};
+    static constexpr Feature AVX512VBMI = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 1};
+    static constexpr Feature UMIP = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 2};
+    static constexpr Feature PKU = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 3};
+    static constexpr Feature OSPKE = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 4};
+    static constexpr Feature AVX512VBMI2 = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 6};
+    static constexpr Feature GFNI = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 8};
+    static constexpr Feature VAES = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 9};
+    static constexpr Feature VPCLMULQDQ = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 10};
+    static constexpr Feature AVX512VNNI = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 11};
+    static constexpr Feature AVX512BITALG = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 12};
+    static constexpr Feature AVX512VPOPCNTDQ = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 14};
+    static constexpr Feature RDPID = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 22};
+    static constexpr Feature SGX_LC = {.leaf = LEAF7, .reg = Registers::ECX, .bit = 30};
+    static constexpr Feature AVX512_4VNNIW = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 2};
+    static constexpr Feature AVX512_4FMAPS = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 3};
+    static constexpr Feature PCONFIG = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 18};
+    static constexpr Feature CLFLUSH = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 19};
+    static constexpr Feature SPEC_CTRL = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 26};
+    static constexpr Feature STIBP = {.leaf = LEAF7, .reg = Registers::EDX, .bit = 27};
+
+    static constexpr Feature LAHF = {.leaf = LEAF8_01, .reg = Registers::ECX, .bit = 0};
+    static constexpr Feature IA64 = {.leaf = LEAF8_01, .reg = Registers::EDX, .bit = 29};
+    static constexpr Feature RDTSCP = {.leaf = LEAF8_01, .reg = Registers::EDX, .bit = 27};
+    static constexpr Feature PDPE1GB = {.leaf = LEAF8_01, .reg = Registers::EDX, .bit = 26};
+    static constexpr Feature XD = {.leaf = LEAF8_01, .reg = Registers::EDX, .bit = 20};
+    static constexpr Feature SYSCALL = {.leaf = LEAF8_01, .reg = Registers::EDX, .bit = 11};
+
+    Features(Registers leaf1, Registers leaf7, Registers leaf8_01);
+
+    inline bool HasFeature(Feature feature) const {
+        DEBUG_ASSERT_MSG(feature.leaf < kLeafCount &&
+                             feature.reg <= Registers::EDX &&
+                             feature.bit <= 32,
+                         "set: %u reg:%u %d bit: %u",
+                         feature.leaf, feature.reg, Registers::EDX, feature.bit);
+        return leaves_[feature.leaf].reg[feature.reg] & (1 << feature.bit);
+    }
+
+    // Returns the maximum supported logical processors in a physical package.
+    // This is NOT that same as the number of logical processors present.
+    uint8_t max_logical_processors_in_package() const;
+
+private:
+    static constexpr size_t kLeafCount = 3;
+
+    const Registers leaves_[kLeafCount];
+};
+
+// Parses topology data from the CPUID instruction.
+class Topology {
+public:
+    static constexpr size_t kMaxLevels = 3;
+    static constexpr uint8_t kInvalidCount = 255;
+    enum class LevelType {
+        INVALID,
+        SMT,
+        CORE,
+        PACKAGE
+    };
+    struct Level {
+        LevelType type = LevelType::INVALID;
+
+        // node_count is set best-effort, only some systems provide it.
+        uint8_t node_count = kInvalidCount;
+
+        uint8_t shift_width = 0;
+    };
+    struct Levels {
+        Level levels[kMaxLevels];
+        uint8_t level_count = 0;
+    };
+
+    Topology(ManufacturerInfo info, Features features,
+             Registers leaf4, Registers* leafB);
+
+    // Provides details for each level of this system's topology.
+    // Returns nullopt if unable to parse topology from cpuid data.
+    std::optional<Levels> levels() const;
+
+private:
+    std::optional<Levels> IntelLevels() const;
+
+    static constexpr size_t kEaxBLevels = 3;
+
+    ManufacturerInfo info_;
+    Features features_;
+
+    Registers leaf4_;
+    Registers leafB_[kEaxBLevels];
+};
+
+// Wraps the CPUID instruction on x86, provides helpers to parse the output and
+// allows unit testing of libraries reading it.
+class CpuId {
+public:
+    virtual ~CpuId() = default;
+    virtual ManufacturerInfo ReadManufacturerInfo() const;
+    virtual ProcessorId ReadProcessorId() const;
+    virtual Features ReadFeatures() const;
+    virtual Topology ReadTopology() const;
+
+private:
+};
+
+} // namespace cpu_id
+
+#endif // KERNEL_ARCH_X86_CPUID_H_
diff --git a/kernel/arch/x86/cpuid/rules.mk b/kernel/arch/x86/cpuid/rules.mk
new file mode 100644
index 0000000..e4f0f04
--- /dev/null
+++ b/kernel/arch/x86/cpuid/rules.mk
@@ -0,0 +1,27 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+# Common Code
+
+LOCAL_SRCS := \
+    $(LOCAL_DIR)/cpuid.cpp \
+    $(LOCAL_DIR)/cpuid_test.cpp \
+
+# system-topology
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_GROUP := core
+
+MODULE_SRCS := $(LOCAL_SRCS)
+
+MODULE_DEPS := \
+    kernel/lib/fbl \
+    kernel/lib/unittest \
+
+MODULE_NAME := cpuid
+
+include make/module.mk
diff --git a/kernel/arch/x86/rules.mk b/kernel/arch/x86/rules.mk
index 07f3bcd..b183c04 100644
--- a/kernel/arch/x86/rules.mk
+++ b/kernel/arch/x86/rules.mk
@@ -74,6 +74,7 @@
 
 MODULE_DEPS += \
 	kernel/arch/x86/page_tables \
+	kernel/arch/x86/cpuid \
 	kernel/dev/iommu/dummy \
 	kernel/dev/iommu/intel \
 	kernel/lib/bitmap \