[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.
This commit is contained in:
Justin C. Miller
2021-01-18 13:26:45 -08:00
parent 55a5c97034
commit c3a0266354
8 changed files with 229 additions and 165 deletions

View File

@@ -8,6 +8,7 @@
#include <stdint.h>
#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);

View File

@@ -1,116 +1,31 @@
#include <stdint.h>
#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<uint32_t *>(&m_vendor_id[0]),
reinterpret_cast<uint32_t *>(&m_vendor_id[8]),
reinterpret_cast<uint32_t *>(&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<uint32_t *>(&m_brand_name[0]),
reinterpret_cast<uint32_t *>(&m_brand_name[4]),
reinterpret_cast<uint32_t *>(&m_brand_name[8]),
reinterpret_cast<uint32_t *>(&m_brand_name[12]));
__cpuid(cpuid_extended + 3, 0,
reinterpret_cast<uint32_t *>(&m_brand_name[16]),
reinterpret_cast<uint32_t *>(&m_brand_name[20]),
reinterpret_cast<uint32_t *>(&m_brand_name[24]),
reinterpret_cast<uint32_t *>(&m_brand_name[28]));
__cpuid(cpuid_extended + 4, 0,
reinterpret_cast<uint32_t *>(&m_brand_name[32]),
reinterpret_cast<uint32_t *>(&m_brand_name[36]),
reinterpret_cast<uint32_t *>(&m_brand_name[40]),
reinterpret_cast<uint32_t *>(&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<uint64_t>(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<uint64_t>(feat))) != 0;
}

View File

@@ -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();

View File

@@ -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];

97
src/libraries/cpu/cpu.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include <stdint.h>
#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<uint32_t *>(&m_vendor_id[0]),
reinterpret_cast<uint32_t *>(&m_vendor_id[8]),
reinterpret_cast<uint32_t *>(&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<uint32_t *>(&m_brand_name[0]),
reinterpret_cast<uint32_t *>(&m_brand_name[4]),
reinterpret_cast<uint32_t *>(&m_brand_name[8]),
reinterpret_cast<uint32_t *>(&m_brand_name[12]));
__cpuid(cpuid_extended + 3, 0,
reinterpret_cast<uint32_t *>(&m_brand_name[16]),
reinterpret_cast<uint32_t *>(&m_brand_name[20]),
reinterpret_cast<uint32_t *>(&m_brand_name[24]),
reinterpret_cast<uint32_t *>(&m_brand_name[28]));
__cpuid(cpuid_extended + 4, 0,
reinterpret_cast<uint32_t *>(&m_brand_name[32]),
reinterpret_cast<uint32_t *>(&m_brand_name[36]),
reinterpret_cast<uint32_t *>(&m_brand_name[40]),
reinterpret_cast<uint32_t *>(&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<uint64_t>(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<uint64_t>(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<uint64_t>(feat))) != 0;
}
}

View File

@@ -0,0 +1,81 @@
#pragma once
/// \file cpu.h Definition of required cpu features for jsix
#include <stdint.h>
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];
};
}