Files
lk/arch/x86/feature.c
Travis Geiselbrecht 664bb17afa [ubsan] fix some bugs and warnings discovered by ubsan
- X86 cpuid feature list dump was using the wrong array and walking off
  the end of one.
- GICv2 code had a left shift by up to 31 of an integer. Needs to be
  unsigned.
- PLIC same as GIC code.
- fdtwalker code should be using a bytewise accessor based helper
  function for reading large integers out of an unaliged FDT.
- PCI BIOS32 search code could do a 32bit unaligned read of a string,
  switch to using memcmp.
2025-10-05 15:35:31 -07:00

291 lines
10 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
struct x86_model_info __x86_model;
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 = MIN(a, __X86_MAX_SUPPORTED_CPUID);;
LTRACEF("cpuid leaf 0: %#x %#x %#x %#x\n", a, b, c, d);
// 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);
LTRACEF("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 = MIN(a, __X86_MAX_SUPPORTED_CPUID_EXT);
}
// read max hypervisor leaf
cpuid(X86_CPUID_HYP_BASE, &a, &b, &c, &d);
// Check that it's an understood hypervisor leaf
if ((b == 0x4b4d564b && c == 0x564b4d56 && d == 0x4d) || /* KVMKVMKVM */
(b == 0x54474354 && c == 0x43544743 && d == 0x47435447)) { /* TCGTCGTCGTCG */
max_cpuid_leaf_hyp = MIN(a, __X86_MAX_SUPPORTED_CPUID_HYP);
} else {
max_cpuid_leaf_hyp = 0;
}
} else {
__x86_cpu_vendor = X86_CPU_VENDOR_INTEL; // intrinsically Intel without cpuid
}
// detect and populate the x86_model structure
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);
__x86_model.processor_type = BITS_SHIFT(a, 13, 12);
__x86_model.family = BITS_SHIFT(a, 11, 8);
__x86_model.model = BITS_SHIFT(a, 7, 4);
__x86_model.stepping = BITS_SHIFT(a, 3, 0);
__x86_model.display_family = __x86_model.family;
__x86_model.display_model = __x86_model.model;
uint32_t ext_family = BITS_SHIFT(a, 27, 20);
uint32_t ext_model = BITS_SHIFT(a, 19, 16);
switch (__x86_model.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) {
__x86_model.display_model |= ext_model << 4; // extended model field extends the regular model
}
break;
case 0xf:
__x86_cpu_level = X86_CPU_LEVEL_PENTIUM_PRO;
__x86_model.display_family += ext_family; // family 0xf stuff is extended by bits 27:20
__x86_model.display_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;
}
}
}
/* early detection of cpu features on cpu 0, before the kernel is scheduling */
void x86_feature_early_init(void) {
x86_cpu_detect();
// cache a copy of the cpuid bits
if (has_cpuid) {
for (uint32_t i = 0; 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; i <= 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; i <= 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);
}
}
}
}
static void x86_feature_dump_cpuid(void) {
for (uint32_t i = X86_CPUID_BASE; i <= max_cpuid_leaf; i++) {
printf("X86: cpuid leaf %#x: %08x %08x %08x %08x\n", i,
saved_cpuids[i - X86_CPUID_BASE].a, saved_cpuids[i - X86_CPUID_BASE].b, saved_cpuids[i - X86_CPUID_BASE].c, saved_cpuids[i - X86_CPUID_BASE].d);
}
for (uint32_t i = X86_CPUID_HYP_BASE; i <= max_cpuid_leaf_hyp; i++) {
uint32_t index = i - X86_CPUID_HYP_BASE;
printf("X86: cpuid leaf %#x: %08x %08x %08x %08x\n", i,
saved_cpuids_hyp[index].a, saved_cpuids_hyp[index].b, saved_cpuids_hyp[index].c, saved_cpuids_hyp[index].d);
}
for (uint32_t i = X86_CPUID_EXT_BASE; i <= max_cpuid_leaf_ext; i++) {
uint32_t index = i - X86_CPUID_EXT_BASE;
printf("X86: cpuid leaf %#x: %08x %08x %08x %08x\n", i,
saved_cpuids_ext[index].a, saved_cpuids_ext[index].b, saved_cpuids_ext[index].c, saved_cpuids_ext[index].d);
}
}
/* later feature init hook, called after the kernel is able to schedule */
void x86_feature_init(void) {
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);
}
if (has_cpuid) {
// read the max basic cpuid leaf
uint32_t a, b, c, d;
cpuid(X86_CPUID_BASE, &a, &b, &c, &d);
// 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;
dprintf(SPEW, "X86: vendor string '%s'\n", vs.str);
}
const struct x86_model_info* model = x86_get_model();
printf("X86: processor model info type %#x family %#x model %#x stepping %#x\n",
model->processor_type, model->family, model->model, model->stepping);
printf("\tdisplay_family %#x display_model %#x\n", model->display_family, model->display_model);
if (has_cpuid && LK_DEBUGLEVEL > 1) {
x86_feature_dump_cpuid();
}
}
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;
}