From 34120fc4c19eb7fe27e69ec7568db36e37673bf0 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Mon, 18 Jan 2021 13:26:45 -0800 Subject: [PATCH] [cpu] Split cpuid validation into separate lib In order to allow the bootloader to do preliminary CPUID validation while UEFI is still handling displaying information to the user, split most of the kernel's CPUID handling into a library to be used by both kernel and boot. --- modules.yaml | 11 ++ src/boot/main.cpp | 24 ++++ src/kernel/cpu.cpp | 109 ++---------------- src/kernel/cpu.h | 69 +---------- src/kernel/main.cpp | 3 +- src/libraries/cpu/cpu.cpp | 97 ++++++++++++++++ src/libraries/cpu/include/cpu/cpu.h | 81 +++++++++++++ .../cpu/include/cpu/features.inc} | 0 8 files changed, 229 insertions(+), 165 deletions(-) create mode 100644 src/libraries/cpu/cpu.cpp create mode 100644 src/libraries/cpu/include/cpu/cpu.h rename src/{kernel/cpu_features.inc => libraries/cpu/include/cpu/features.inc} (100%) diff --git a/modules.yaml b/modules.yaml index ceff427..75bc913 100644 --- a/modules.yaml +++ b/modules.yaml @@ -6,6 +6,7 @@ modules: output: jsix.elf target: host deps: + - cpu - kutil includes: - src/kernel @@ -62,6 +63,8 @@ modules: kind: exe target: boot output: boot.efi + deps: + - cpu source: - src/boot/main.cpp - src/boot/console.cpp @@ -110,6 +113,14 @@ modules: - src/libraries/kutil/memory.cpp - src/libraries/kutil/printf.c + cpu: + kind: lib + output: libcpu.a + includes: + - src/libraries/cpu/include + source: + - src/libraries/cpu/cpu.cpp + libc: kind: lib diff --git a/src/boot/main.cpp b/src/boot/main.cpp index 3f74ee4..f48e3b8 100644 --- a/src/boot/main.cpp +++ b/src/boot/main.cpp @@ -8,6 +8,7 @@ #include #include "console.h" +#include "cpu/cpu.h" #include "error.h" #include "fs.h" #include "hardware.h" @@ -94,6 +95,28 @@ add_module(args::header *args, args::mod_type type, buffer &data) m.size = data.size; } +/// Check that all required cpu features are supported +void +check_cpu_supported() +{ + status_line status {L"Checking CPU features"}; + + cpu::cpu_id cpu; + uint64_t missing = cpu.missing(); + if (missing) { +#define CPU_FEATURE_OPT(...) +#define CPU_FEATURE_REQ(name, ...) \ + if (!cpu.has_feature(cpu::feature::name)) { \ + status::fail(L"CPU required feature " L ## #name, uefi::status::unsupported); \ + } +#include "cpu/features.inc" +#undef CPU_FEATURE_REQ +#undef CPU_FEATURE_OPT + + error::raise(uefi::status::unsupported, L"CPU not supported"); + } +} + /// The main procedure for the portion of the loader that runs while /// UEFI is still in control of the machine. (ie, while the loader still /// has access to boot services. @@ -160,6 +183,7 @@ efi_main(uefi::handle image, uefi::system_table *st) { using namespace boot; console con(st->boot_services, st->con_out); + check_cpu_supported(); args::header *args = uefi_preboot(image, st); memory::efi_mem_map map = uefi_exit(args, image, st->boot_services); diff --git a/src/kernel/cpu.cpp b/src/kernel/cpu.cpp index 7e6c25b..4b0720e 100644 --- a/src/kernel/cpu.cpp +++ b/src/kernel/cpu.cpp @@ -1,116 +1,31 @@ #include +#include "kutil/assert.h" #include "kutil/memory.h" #include "cpu.h" +#include "cpu/cpu.h" #include "log.h" cpu_data bsp_cpu_data; -static constexpr uint32_t cpuid_extended = 0x80000000; - - -inline static void -__cpuid( - uint32_t leaf, - uint32_t subleaf, - uint32_t *eax, - uint32_t *ebx = nullptr, - uint32_t *ecx = nullptr, - uint32_t *edx = nullptr) -{ - uint32_t a, b, c, d; - __asm__ __volatile__ ( "cpuid" - : "=a"(a), "=b"(b), "=c"(c), "=d"(d) - : "a"(leaf), "c"(subleaf) - ); - if (eax) *eax = a; - if (ebx) *ebx = b; - if (ecx) *ecx = c; - if (edx) *edx = d; -} - -cpu_id::cpu_id() : - m_features(0) -{ - __cpuid(0, 0, - &m_high_basic, - reinterpret_cast(&m_vendor_id[0]), - reinterpret_cast(&m_vendor_id[8]), - reinterpret_cast(&m_vendor_id[4])); - - __cpuid(cpuid_extended, 0, &m_high_ext); - - if (m_high_ext >= cpuid_extended + 4) { - __cpuid(cpuid_extended + 2, 0, - reinterpret_cast(&m_brand_name[0]), - reinterpret_cast(&m_brand_name[4]), - reinterpret_cast(&m_brand_name[8]), - reinterpret_cast(&m_brand_name[12])); - __cpuid(cpuid_extended + 3, 0, - reinterpret_cast(&m_brand_name[16]), - reinterpret_cast(&m_brand_name[20]), - reinterpret_cast(&m_brand_name[24]), - reinterpret_cast(&m_brand_name[28])); - __cpuid(cpuid_extended + 4, 0, - reinterpret_cast(&m_brand_name[32]), - reinterpret_cast(&m_brand_name[36]), - reinterpret_cast(&m_brand_name[40]), - reinterpret_cast(&m_brand_name[44])); - } else { - m_brand_name[0] = 0; - } -} - -cpu_id::regs -cpu_id::get(uint32_t leaf, uint32_t sub) const -{ - regs ret {0, 0, 0, 0}; - - if ((leaf & cpuid_extended) == 0 && leaf > m_high_basic) return ret; - if ((leaf & cpuid_extended) != 0 && leaf > m_high_ext) return ret; - - __cpuid(leaf, sub, &ret.eax, &ret.ebx, &ret.ecx, &ret.edx); - return ret; -} - void -cpu_id::validate() +cpu_validate() { - bool fail = false; - uint32_t leaf = 0; - uint32_t sub = 0; - regs r; + cpu::cpu_id cpu; - log::info(logs::boot, "CPU: %s", brand_name()); - log::debug(logs::boot, " Vendor is %s", vendor_id()); + log::info(logs::boot, "CPU: %s", cpu.brand_name()); + log::debug(logs::boot, " Vendor is %s", cpu.vendor_id()); - log::debug(logs::boot, " Higest basic CPUID: 0x%02x", highest_basic()); - log::debug(logs::boot, " Higest ext CPUID: 0x%02x", highest_ext() & ~cpuid_extended); + log::debug(logs::boot, " Higest basic CPUID: 0x%02x", cpu.highest_basic()); + log::debug(logs::boot, " Higest ext CPUID: 0x%02x", cpu.highest_ext() & ~cpu::cpu_id::cpuid_extended); -#define CPU_FEATURE_OPT(name, feat_leaf, feat_sub, regname, bit) \ - if (leaf != feat_leaf || sub != feat_sub) { \ - leaf = feat_leaf; sub = feat_sub; r = get(leaf, sub); \ - } \ - if (r.regname & (1ull << bit)) \ - m_features |= (1ull << static_cast(cpu_feature::name)); \ - log::debug(logs::boot, " Supports %9s: %s", #name, (r.regname & (1ull << bit)) ? "yes" : "no"); +#define CPU_FEATURE_OPT(name, ...) \ + log::debug(logs::boot, " Supports %9s: %s", #name, cpu.has_feature(cpu::feature::name) ? "yes" : "no"); #define CPU_FEATURE_REQ(name, feat_leaf, feat_sub, regname, bit) \ CPU_FEATURE_OPT(name, feat_leaf, feat_sub, regname, bit); \ - if ((r.regname & (1ull << bit)) == 0) { \ - log::error(logs::boot, "CPU missing required feature " #name); \ - fail = true; \ - } + kassert(cpu.has_feature(cpu::feature::name), "Missing required CPU feature " #name ); -#include "cpu_features.inc" +#include "cpu/features.inc" #undef CPU_FEATURE_OPT #undef CPU_FEATURE_REQ - - if (fail) - log::fatal(logs::boot, "CPU not supported."); -} - -bool -cpu_id::has_feature(cpu_feature feat) -{ - return (m_features & (1 << static_cast(feat))) != 0; } diff --git a/src/kernel/cpu.h b/src/kernel/cpu.h index 84108fd..ed5722d 100644 --- a/src/kernel/cpu.h +++ b/src/kernel/cpu.h @@ -27,69 +27,6 @@ struct cpu_data extern cpu_data bsp_cpu_data; -/// Enum of the cpu features jsix cares about -enum class cpu_feature { -#define CPU_FEATURE_REQ(name, ...) name, -#define CPU_FEATURE_OPT(name, ...) name, -#include "cpu_features.inc" -#undef CPU_FEATURE_OPT -#undef CPU_FEATURE_REQ - max -}; - -class cpu_id -{ -public: - /// CPUID result register values - struct regs { - union { - uint32_t reg[4]; - uint32_t eax, ebx, ecx, edx; - }; - - /// Return true if bit |bit| of EAX is set - bool eax_bit(unsigned bit) { return (eax >> bit) & 0x1; } - - /// Return true if bit |bit| of EBX is set - bool ebx_bit(unsigned bit) { return (ebx >> bit) & 0x1; } - - /// Return true if bit |bit| of ECX is set - bool ecx_bit(unsigned bit) { return (ecx >> bit) & 0x1; } - - /// Return true if bit |bit| of EDX is set - bool edx_bit(unsigned bit) { return (edx >> bit) & 0x1; } - }; - - cpu_id(); - - /// The the result of a given CPUID leaf/subleaf - /// \arg leaf The leaf selector (initial EAX) - /// \arg subleaf The subleaf selector (initial ECX) - /// \returns A |regs| struct of the values retuned - regs get(uint32_t leaf, uint32_t sub = 0) const; - - /// Get the name of the cpu vendor (eg, "GenuineIntel") - inline const char * vendor_id() const { return m_vendor_id; } - - /// Get the brand name of this processor model - inline const char * brand_name() const { return m_brand_name; } - - /// Get the highest basic CPUID leaf supported - inline uint32_t highest_basic() const { return m_high_basic; } - - /// Get the highest extended CPUID leaf supported - inline uint32_t highest_ext() const { return m_high_ext; } - - /// Validate the CPU supports the necessary options for jsix - void validate(); - - /// Return true if the CPU claims to support the given feature - bool has_feature(cpu_feature feat); - -private: - uint32_t m_high_basic; - uint32_t m_high_ext; - char m_vendor_id[13]; - char m_brand_name[48]; - uint64_t m_features; -}; +// We already validated the required options in the bootloader, +// but iterate the options and log about them. +void cpu_validate(); diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index 120a2b2..e872f1d 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -135,8 +135,7 @@ kernel_main(args::header *header) run_constructors(); memory_initialize_post_ctors(header); - cpu_id cpu; - cpu.validate(); + cpu_validate(); for (size_t i = 0; i < header->num_modules; ++i) { args::module &mod = header->modules[i]; diff --git a/src/libraries/cpu/cpu.cpp b/src/libraries/cpu/cpu.cpp new file mode 100644 index 0000000..98395e5 --- /dev/null +++ b/src/libraries/cpu/cpu.cpp @@ -0,0 +1,97 @@ +#include +#include "cpu/cpu.h" + +namespace cpu { + +inline static void +__cpuid( + uint32_t leaf, + uint32_t subleaf, + uint32_t *eax, + uint32_t *ebx = nullptr, + uint32_t *ecx = nullptr, + uint32_t *edx = nullptr) +{ + uint32_t a, b, c, d; + __asm__ __volatile__ ( "cpuid" + : "=a"(a), "=b"(b), "=c"(c), "=d"(d) + : "a"(leaf), "c"(subleaf) + ); + if (eax) *eax = a; + if (ebx) *ebx = b; + if (ecx) *ecx = c; + if (edx) *edx = d; +} + +cpu_id::cpu_id() : + m_features {0}, + m_missing {0} +{ + __cpuid(0, 0, + &m_high_basic, + reinterpret_cast(&m_vendor_id[0]), + reinterpret_cast(&m_vendor_id[8]), + reinterpret_cast(&m_vendor_id[4])); + + __cpuid(cpuid_extended, 0, &m_high_ext); + + if (m_high_ext >= cpuid_extended + 4) { + __cpuid(cpuid_extended + 2, 0, + reinterpret_cast(&m_brand_name[0]), + reinterpret_cast(&m_brand_name[4]), + reinterpret_cast(&m_brand_name[8]), + reinterpret_cast(&m_brand_name[12])); + __cpuid(cpuid_extended + 3, 0, + reinterpret_cast(&m_brand_name[16]), + reinterpret_cast(&m_brand_name[20]), + reinterpret_cast(&m_brand_name[24]), + reinterpret_cast(&m_brand_name[28])); + __cpuid(cpuid_extended + 4, 0, + reinterpret_cast(&m_brand_name[32]), + reinterpret_cast(&m_brand_name[36]), + reinterpret_cast(&m_brand_name[40]), + reinterpret_cast(&m_brand_name[44])); + } else { + m_brand_name[0] = 0; + } + + uint32_t leaf = -1u; + uint32_t sub = -1u; + regs r; +#define CPU_FEATURE_OPT(name, feat_leaf, feat_sub, regname, bit) \ + if (leaf != feat_leaf || sub != feat_sub) { \ + leaf = feat_leaf; sub = feat_sub; r = get(leaf, sub); \ + } \ + if (r.regname & (1ull << bit)) \ + m_features |= (1ull << static_cast(feature::name)); \ + +#define CPU_FEATURE_REQ(name, feat_leaf, feat_sub, regname, bit) \ + CPU_FEATURE_OPT(name, feat_leaf, feat_sub, regname, bit); \ + if ((r.regname & (1ull << bit)) == 0) { \ + m_missing |= (1ull << static_cast(feature::name)); \ + } + +#include "cpu/features.inc" +#undef CPU_FEATURE_OPT +#undef CPU_FEATURE_REQ +} + +cpu_id::regs +cpu_id::get(uint32_t leaf, uint32_t sub) const +{ + regs ret {0, 0, 0, 0}; + + if ((leaf & cpuid_extended) == 0 && leaf > m_high_basic) return ret; + if ((leaf & cpuid_extended) != 0 && leaf > m_high_ext) return ret; + + __cpuid(leaf, sub, &ret.eax, &ret.ebx, &ret.ecx, &ret.edx); + return ret; +} + +bool +cpu_id::has_feature(feature feat) +{ + return (m_features & (1 << static_cast(feat))) != 0; +} + +} diff --git a/src/libraries/cpu/include/cpu/cpu.h b/src/libraries/cpu/include/cpu/cpu.h new file mode 100644 index 0000000..ceace0f --- /dev/null +++ b/src/libraries/cpu/include/cpu/cpu.h @@ -0,0 +1,81 @@ +#pragma once +/// \file cpu.h Definition of required cpu features for jsix + +#include + +namespace cpu { + +/// Enum of the cpu features jsix cares about +enum class feature { +#define CPU_FEATURE_REQ(name, ...) name, +#define CPU_FEATURE_OPT(name, ...) name, +#include "cpu/features.inc" +#undef CPU_FEATURE_OPT +#undef CPU_FEATURE_REQ + max +}; + +class cpu_id +{ +public: + static constexpr uint32_t cpuid_extended = 0x80000000; + + /// CPUID result register values + struct regs { + union { + uint32_t reg[4]; + uint32_t eax, ebx, ecx, edx; + }; + + /// Return true if bit |bit| of EAX is set + bool eax_bit(unsigned bit) { return (eax >> bit) & 0x1; } + + /// Return true if bit |bit| of EBX is set + bool ebx_bit(unsigned bit) { return (ebx >> bit) & 0x1; } + + /// Return true if bit |bit| of ECX is set + bool ecx_bit(unsigned bit) { return (ecx >> bit) & 0x1; } + + /// Return true if bit |bit| of EDX is set + bool edx_bit(unsigned bit) { return (edx >> bit) & 0x1; } + }; + + cpu_id(); + + /// The the result of a given CPUID leaf/subleaf + /// \arg leaf The leaf selector (initial EAX) + /// \arg subleaf The subleaf selector (initial ECX) + /// \returns A |regs| struct of the values retuned + regs get(uint32_t leaf, uint32_t sub = 0) const; + + /// Get the name of the cpu vendor (eg, "GenuineIntel") + inline const char * vendor_id() const { return m_vendor_id; } + + /// Get the brand name of this processor model + inline const char * brand_name() const { return m_brand_name; } + + /// Get the highest basic CPUID leaf supported + inline uint32_t highest_basic() const { return m_high_basic; } + + /// Get the highest extended CPUID leaf supported + inline uint32_t highest_ext() const { return m_high_ext; } + + /// Get which required options are missing as flags + inline uint64_t missing() const { return m_missing; } + + /// Validate the CPU supports the necessary options for jsix + inline bool supported() const { return m_missing; } + + /// Return true if the CPU claims to support the given feature + bool has_feature(feature feat); + +private: + uint32_t m_high_basic; + uint32_t m_high_ext; + uint64_t m_features; + uint64_t m_missing; + char m_vendor_id[13]; + char m_brand_name[48]; +}; + +} diff --git a/src/kernel/cpu_features.inc b/src/libraries/cpu/include/cpu/features.inc similarity index 100% rename from src/kernel/cpu_features.inc rename to src/libraries/cpu/include/cpu/features.inc