Files
lk/arch/x86/feature.c
Travis Geiselbrecht 7505c3087a [arch][x86][fpu] spiff up the fpu code to detect things more cleanly
Doesn't really change the functionality of the code except it'll try to
work with less fpu features present.
2022-07-24 19:56:24 -07:00

237 lines
8.0 KiB
C

/*
* Copyright (c) 2019 Travis Geiselbrecht
* Copyright 2016 The Fuchsia Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <arch/x86/feature.h>
#include <lk/bits.h>
#include <lk/debug.h>
#include <lk/trace.h>
#include <arch/x86.h>
#include <assert.h>
#include <string.h>
#define LOCAL_TRACE 0
enum x86_cpu_vendor __x86_cpu_vendor = X86_CPU_VENDOR_INTEL;
enum x86_cpu_level __x86_cpu_level = X86_CPU_LEVEL_386; // start off assuming 386
bool has_cpuid = false;
/* a saved cache of three banks of cpuids loaded a boot */
struct x86_cpuid_leaf saved_cpuids[__X86_MAX_SUPPORTED_CPUID + 1];
struct x86_cpuid_leaf saved_cpuids_hyp[__X86_MAX_SUPPORTED_CPUID_HYP - X86_CPUID_HYP_BASE + 1];
struct x86_cpuid_leaf saved_cpuids_ext[__X86_MAX_SUPPORTED_CPUID_EXT - X86_CPUID_EXT_BASE + 1];
uint32_t max_cpuid_leaf = 0;
uint32_t max_cpuid_leaf_hyp = 0;
uint32_t max_cpuid_leaf_ext = 0;
static enum x86_cpu_vendor match_cpu_vendor_string(const char *str) {
// from table at https://www.sandpile.org/x86/cpuid.htm#level_0000_0000h
if (!strcmp(str, "GenuineIntel")) {
return X86_CPU_VENDOR_INTEL;
}
if (!strcmp(str, "UMC UMC UMC ")) {
return X86_CPU_VENDOR_UMC;
}
if (!strcmp(str, "AuthenticAMD")) {
return X86_CPU_VENDOR_AMD;
}
if (!strcmp(str, "CyrixInstead")) {
return X86_CPU_VENDOR_CYRIX;
}
if (!strcmp(str, "NexGenDriven")) {
return X86_CPU_VENDOR_NEXGEN;
}
if (!strcmp(str, "CentaurHauls")) {
return X86_CPU_VENDOR_CENTAUR;
}
if (!strcmp(str, "RiseRiseRise")) {
return X86_CPU_VENDOR_RISE;
}
if (!strcmp(str, "SiS SiS SiS ")) {
return X86_CPU_VENDOR_SIS;
}
if (!strcmp(str, "GenuineTMx86")) {
return X86_CPU_VENDOR_TRANSMETA;
}
if (!strcmp(str, "Geode by NSC")) {
return X86_CPU_VENDOR_NSC;
}
return X86_CPU_VENDOR_UNKNOWN;
}
static void x86_cpu_detect(void) {
if (X86_LEGACY) {
// inspired by http://www.rcollins.org/ddj/Sep96/Sep96.html
// try to detect a 486
// set the EFLAGS.AC bit, see if it sets
uint32_t flags = x86_save_flags();
x86_restore_flags(flags | X86_FLAGS_AC);
if (x86_save_flags() & X86_FLAGS_AC) {
__x86_cpu_level = X86_CPU_LEVEL_486;
// test EFLAGS.ID flag
x86_restore_flags(flags | X86_FLAGS_ID);
if (x86_save_flags() & X86_FLAGS_ID) {
has_cpuid = true;
}
}
} else {
// at least a pentium and has cpuid
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM;
has_cpuid = true;
}
if (has_cpuid) {
uint32_t a, b, c, d;
// read the max basic cpuid leaf
cpuid(X86_CPUID_BASE, &a, &b, &c, &d);
max_cpuid_leaf = a;
// read the vendor string
union {
uint32_t reg[3];
char str[13];
} vs;
vs.reg[0] = b;
vs.reg[1] = d;
vs.reg[2] = c;
vs.str[12] = 0;
__x86_cpu_vendor = match_cpu_vendor_string(vs.str);
dprintf(SPEW, "x86: vendor string '%s' from cpuid\n", vs.str);
// read max extended cpuid leaf
cpuid(X86_CPUID_EXT_BASE, &a, &b, &c, &d);
if (a >= X86_CPUID_EXT_BASE) {
max_cpuid_leaf_ext = a;
}
// read max hypervisor leaf
cpuid(X86_CPUID_HYP_BASE, &a, &b, &c, &d);
if (a >= X86_CPUID_HYP_BASE) {
max_cpuid_leaf_hyp = a;
}
} else {
__x86_cpu_vendor = X86_CPU_VENDOR_INTEL; // intrinsically Intel without cpuid
}
// do a quick cpu level detection using cpuid
if (has_cpuid && max_cpuid_leaf >= 1) {
uint32_t a, b, c, d;
cpuid(X86_CPUID_MODEL_FEATURES, &a, &b, &c, &d);
LTRACEF("cpuid leaf 1: %#x %#x %#x %#x\n", a, b, c, d);
uint32_t ext_family = BITS_SHIFT(a, 27, 20);
uint32_t ext_model = BITS_SHIFT(a, 19, 16);
uint32_t family = BITS_SHIFT(a, 11, 8);
uint32_t model = BITS_SHIFT(a, 7, 4);
LTRACEF("raw family %#x model %#x ext_family %#x ext_model %#x\n", family, model, ext_family, ext_model);
switch (family) {
case 4:
__x86_cpu_level = X86_CPU_LEVEL_486;
break;
case 5:
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM;
break;
case 6:
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM_PRO;
if (x86_get_cpu_vendor() == X86_CPU_VENDOR_INTEL) {
model |= ext_model << 4; // extended model field extends the regular model
}
break;
case 0xf:
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM_PRO;
family += ext_family; // family 0xf stuff is extended by bits 27:20
model |= ext_model << 4; // extended model field extends the regular model
break;
default:
// unhandled decode, assume ppro+ level
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM_PRO;
break;
}
dprintf(SPEW, "x86: family %#x model %#x\n", family, model);
// TODO: save this information for future use
}
dprintf(SPEW, "x86: detected cpu level %d has_cpuid %d\n", x86_get_cpu_level(), has_cpuid);
if (has_cpuid) {
dprintf(SPEW, "x86: max cpuid leaf %#x ext %#x hyp %#x\n",
max_cpuid_leaf, max_cpuid_leaf_ext, max_cpuid_leaf_hyp);
}
}
void x86_feature_early_init(void) {
x86_cpu_detect();
// cache a copy of the cpuid bits
if (has_cpuid) {
for (uint32_t i = 1; i <= max_cpuid_leaf; i++) {
cpuid_c(i, 0, &saved_cpuids[i].a, &saved_cpuids[i].b, &saved_cpuids[i].c, &saved_cpuids[i].d);
}
if (max_cpuid_leaf_ext > 0) {
for (uint32_t i = X86_CPUID_EXT_BASE + 1; i - 1 < max_cpuid_leaf_ext; i++) {
uint32_t index = i - X86_CPUID_EXT_BASE;
cpuid_c(i, 0, &saved_cpuids_ext[index].a, &saved_cpuids_ext[index].b, &saved_cpuids_ext[index].c,
&saved_cpuids_ext[index].d);
}
}
if (max_cpuid_leaf_hyp > 0) {
for (uint32_t i = X86_CPUID_HYP_BASE + 1; i - 1 < max_cpuid_leaf_hyp; i++) {
uint32_t index = i - X86_CPUID_HYP_BASE;
cpuid_c(i, 0, &saved_cpuids_hyp[index].a, &saved_cpuids_hyp[index].b, &saved_cpuids_hyp[index].c,
&saved_cpuids_hyp[index].d);
}
}
}
}
void x86_feature_init(void) {
}
bool x86_get_cpuid_subleaf(enum x86_cpuid_leaf_num num, uint32_t subleaf, struct x86_cpuid_leaf* leaf) {
// make sure the leaf number is within the detected range of the three blocks we know about
if (num < X86_CPUID_HYP_BASE) {
if (num > max_cpuid_leaf) {
return false;
}
} else if (num < X86_CPUID_EXT_BASE) {
if (num > max_cpuid_leaf_hyp) {
return false;
}
} else if (num > max_cpuid_leaf_ext) {
return false;
}
cpuid_c((uint32_t)num, subleaf, &leaf->a, &leaf->b, &leaf->c, &leaf->d);
return true;
}