[U-Boot] [PATCH 1/4] x86: Display more detailed CPU information on boot

Bin Meng bmeng.cn at gmail.com
Tue Nov 4 15:58:03 CET 2014


Currently only basic CPU information (x86 or x86_64) is displayed
on boot. This commit adds more detailed information output including
CPU vendor name, device id, family, model and stepping as well as
the CPU brand string, all of which are extracted from CPUID result.

The CPU identification happens in x86_cpu_init_f() and corresponding
fields are saved in the global data. Later print_cpuinfo() just uses
these fields to display CPU information without the need to probe
again in real time.

Signed-off-by: Bin Meng <bmeng.cn at gmail.com>
---
 arch/x86/cpu/cpu.c                 | 282 +++++++++++++++++++++++++++++++------
 arch/x86/include/asm/cpu.h         | 142 +++++++++++++++++++
 arch/x86/include/asm/global_data.h |   5 +
 3 files changed, 385 insertions(+), 44 deletions(-)

diff --git a/arch/x86/cpu/cpu.c b/arch/x86/cpu/cpu.c
index 2e25253..e9058f7 100644
--- a/arch/x86/cpu/cpu.c
+++ b/arch/x86/cpu/cpu.c
@@ -13,6 +13,9 @@
  * Sysgo Real-Time Solutions, GmbH <www.elinos.com>
  * Alex Zuepke <azu at sysgo.de>
  *
+ * Part of this file is adapted from coreboot
+ * src/arch/x86/lib/cpu.c
+ *
  * SPDX-License-Identifier:	GPL-2.0+
  */
 
@@ -27,6 +30,8 @@
 #include <asm/interrupt.h>
 #include <linux/compiler.h>
 
+DECLARE_GLOBAL_DATA_PTR;
+
 /*
  * Constructor for a conventional segment GDT (or LDT) entry
  * This is a macro so it can be used in initialisers
@@ -43,6 +48,51 @@ struct gdt_ptr {
 	u32 ptr;
 } __packed;
 
+struct cpu_device_id {
+	unsigned vendor;
+	unsigned device;
+};
+
+struct cpuinfo_x86 {
+        uint8_t    x86;            /* CPU family */
+        uint8_t    x86_vendor;     /* CPU vendor */
+        uint8_t    x86_model;
+        uint8_t    x86_mask;
+};
+
+/* List of cpu vendor strings along with their normalized
+ * id values.
+ */
+static struct {
+	int vendor;
+	const char *name;
+} x86_vendors[] = {
+	{ X86_VENDOR_INTEL,     "GenuineIntel", },
+	{ X86_VENDOR_CYRIX,     "CyrixInstead", },
+	{ X86_VENDOR_AMD,       "AuthenticAMD", },
+	{ X86_VENDOR_UMC,       "UMC UMC UMC ", },
+	{ X86_VENDOR_NEXGEN,    "NexGenDriven", },
+	{ X86_VENDOR_CENTAUR,   "CentaurHauls", },
+	{ X86_VENDOR_RISE,      "RiseRiseRise", },
+	{ X86_VENDOR_TRANSMETA, "GenuineTMx86", },
+	{ X86_VENDOR_TRANSMETA, "TransmetaCPU", },
+	{ X86_VENDOR_NSC,       "Geode by NSC", },
+	{ X86_VENDOR_SIS,       "SiS SiS SiS ", },
+};
+
+static const char *x86_vendor_name[] = {
+	[X86_VENDOR_INTEL]     = "Intel",
+	[X86_VENDOR_CYRIX]     = "Cyrix",
+	[X86_VENDOR_AMD]       = "AMD",
+	[X86_VENDOR_UMC]       = "UMC",
+	[X86_VENDOR_NEXGEN]    = "NexGen",
+	[X86_VENDOR_CENTAUR]   = "Centaur",
+	[X86_VENDOR_RISE]      = "Rise",
+	[X86_VENDOR_TRANSMETA] = "Transmeta",
+	[X86_VENDOR_NSC]       = "NSC",
+	[X86_VENDOR_SIS]       = "SiS",
+};
+
 static void load_ds(u32 segment)
 {
 	asm volatile("movl %0, %%ds" : : "r" (segment * X86_GDT_ENTRY_SIZE));
@@ -115,6 +165,131 @@ int __weak x86_cleanup_before_linux(void)
 	return 0;
 }
 
+/*
+ * Cyrix CPUs without cpuid or with cpuid not yet enabled can be detected
+ * by the fact that they preserve the flags across the division of 5/2.
+ * PII and PPro exhibit this behavior too, but they have cpuid available.
+ */
+
+/*
+ * Perform the Cyrix 5/2 test. A Cyrix won't change
+ * the flags, while other 486 chips will.
+ */
+static inline int test_cyrix_52div(void)
+{
+	unsigned int test;
+
+	__asm__ __volatile__(
+	     "sahf\n\t"		/* clear flags (%eax = 0x0005) */
+	     "div %b2\n\t"	/* divide 5 by 2 */
+	     "lahf"		/* store flags into %ah */
+	     : "=a" (test)
+	     : "0" (5), "q" (2)
+	     : "cc");
+
+	/* AH is 0x02 on Cyrix after the divide.. */
+	return (unsigned char) (test >> 8) == 0x02;
+}
+
+/*
+ *	Detect a NexGen CPU running without BIOS hypercode new enough
+ *	to have CPUID. (Thanks to Herbert Oppmann)
+ */
+
+static int deep_magic_nexgen_probe(void)
+{
+	int ret;
+
+	__asm__ __volatile__ (
+		"	movw	$0x5555, %%ax\n"
+		"	xorw	%%dx,%%dx\n"
+		"	movw	$2, %%cx\n"
+		"	divw	%%cx\n"
+		"	movl	$0, %%eax\n"
+		"	jnz	1f\n"
+		"	movl	$1, %%eax\n"
+		"1:\n"
+		: "=a" (ret) : : "cx", "dx" );
+	return  ret;
+}
+
+static bool has_cpuid(void)
+{
+	return flag_is_changeable_p(X86_EFLAGS_ID);
+}
+
+static void identify_cpu(struct cpu_device_id *cpu)
+{
+	char vendor_name[16];
+	int i;
+
+	vendor_name[0] = '\0'; /* Unset */
+
+	/* Find the id and vendor_name */
+	if (!has_cpuid()) {
+		/* Its a 486 if we can modify the AC flag */
+		if (flag_is_changeable_p(X86_EFLAGS_AC)) {
+			cpu->device = 0x00000400; /* 486 */
+		} else {
+			cpu->device = 0x00000300; /* 386 */
+		}
+		if ((cpu->device == 0x00000400) && test_cyrix_52div()) {
+			memcpy(vendor_name, "CyrixInstead", 13);
+			/* If we ever care we can enable cpuid here */
+		}
+		/* Detect NexGen with old hypercode */
+		else if (deep_magic_nexgen_probe()) {
+			memcpy(vendor_name, "NexGenDriven", 13);
+		}
+	}
+	if (has_cpuid()) {
+		int  cpuid_level;
+		struct cpuid_result result;
+		result = cpuid(0x00000000);
+		cpuid_level    = result.eax;
+		vendor_name[ 0] = (result.ebx >>  0) & 0xff;
+		vendor_name[ 1] = (result.ebx >>  8) & 0xff;
+		vendor_name[ 2] = (result.ebx >> 16) & 0xff;
+		vendor_name[ 3] = (result.ebx >> 24) & 0xff;
+		vendor_name[ 4] = (result.edx >>  0) & 0xff;
+		vendor_name[ 5] = (result.edx >>  8) & 0xff;
+		vendor_name[ 6] = (result.edx >> 16) & 0xff;
+		vendor_name[ 7] = (result.edx >> 24) & 0xff;
+		vendor_name[ 8] = (result.ecx >>  0) & 0xff;
+		vendor_name[ 9] = (result.ecx >>  8) & 0xff;
+		vendor_name[10] = (result.ecx >> 16) & 0xff;
+		vendor_name[11] = (result.ecx >> 24) & 0xff;
+		vendor_name[12] = '\0';
+
+		/* Intel-defined flags: level 0x00000001 */
+		if (cpuid_level >= 0x00000001) {
+			cpu->device = cpuid_eax(0x00000001);
+		}
+		else {
+			/* Have CPUID level 0 only unheard of */
+			cpu->device = 0x00000400;
+		}
+	}
+	cpu->vendor = X86_VENDOR_UNKNOWN;
+	for(i = 0; i < ARRAY_SIZE(x86_vendors); i++) {
+		if (memcmp(vendor_name, x86_vendors[i].name, 12) == 0) {
+			cpu->vendor = x86_vendors[i].vendor;
+			break;
+		}
+	}
+}
+
+static inline void get_fms(struct cpuinfo_x86 *c, uint32_t tfms)
+{
+	c->x86 = (tfms >> 8) & 0xf;
+	c->x86_model = (tfms >> 4) & 0xf;
+	c->x86_mask = tfms & 0xf;
+	if (c->x86 == 0xf)
+		c->x86 += (tfms >> 20) & 0xff;
+	if (c->x86 >= 0x6)
+		c->x86_model += ((tfms >> 16) & 0xF) << 4;
+}
+
 int x86_cpu_init_f(void)
 {
 	const u32 em_rst = ~X86_CR0_EM;
@@ -128,6 +303,20 @@ int x86_cpu_init_f(void)
 	     "movl %%eax, %%cr0\n" \
 	     : : "i" (em_rst), "i" (mp_ne_set) : "eax");
 
+	/* identify CPU via cpuid and store the decoded info into gd->arch */
+	if (has_cpuid()) {
+		struct cpu_device_id cpu;
+		struct cpuinfo_x86 c;
+
+		identify_cpu(&cpu);
+		get_fms(&c, cpu.device);
+		gd->arch.x86 = c.x86;
+		gd->arch.x86_vendor = cpu.vendor;
+		gd->arch.x86_model = c.x86_model;
+		gd->arch.x86_mask = c.x86_mask;
+		gd->arch.x86_device = cpu.device;
+	}
+
 	return 0;
 }
 int cpu_init_f(void) __attribute__((weak, alias("x86_cpu_init_f")));
@@ -279,55 +468,14 @@ void cpu_disable_paging_pae(void)
 		: "eax");
 }
 
-static bool has_cpuid(void)
-{
-	unsigned long flag;
-
-	asm volatile("pushf\n" \
-		"pop %%eax\n"
-		"mov %%eax, %%ecx\n"	/* ecx = flags */
-		"xor %1, %%eax\n"
-		"push %%eax\n"
-		"popf\n"		/* flags ^= $2 */
-		"pushf\n"
-		"pop %%eax\n"		/* eax = flags */
-		"push %%ecx\n"
-		"popf\n"		/* flags = ecx */
-		"xor %%ecx, %%eax\n"
-		"mov %%eax, %0"
-		: "=r" (flag)
-		: "i" (1 << 21)
-		: "eax", "ecx", "memory");
-
-	return flag != 0;
-}
-
 static bool can_detect_long_mode(void)
 {
-	unsigned long flag;
-
-	asm volatile("mov $0x80000000, %%eax\n"
-		"cpuid\n"
-		"mov %%eax, %0"
-		: "=r" (flag)
-		:
-		: "eax", "ebx", "ecx", "edx", "memory");
-
-	return flag > 0x80000000UL;
+	return cpuid_eax(0x80000000) > 0x80000000UL;
 }
 
 static bool has_long_mode(void)
 {
-	unsigned long flag;
-
-	asm volatile("mov $0x80000001, %%eax\n"
-		"cpuid\n"
-		"mov %%edx, %0"
-		: "=r" (flag)
-		:
-		: "eax", "ebx", "ecx", "edx", "memory");
-
-	return flag & (1 << 29) ? true : false;
+	return cpuid_edx(0x80000001) & (1 << 29) ? true : false;
 }
 
 int cpu_has_64bit(void)
@@ -336,9 +484,55 @@ int cpu_has_64bit(void)
 		has_long_mode();
 }
 
+static const char *cpu_vendor_name(int vendor)
+{
+	const char *name;
+	name = "<invalid cpu vendor>";
+	if ((vendor < (ARRAY_SIZE(x86_vendor_name))) &&
+		(x86_vendor_name[vendor] != 0))
+	{
+		name = x86_vendor_name[vendor];
+	}
+	return name;
+}
+
+static void fill_processor_name(char *processor_name)
+{
+	struct cpuid_result regs;
+	char temp_processor_name[49];
+	char *processor_name_start;
+	unsigned int *name_as_ints = (unsigned int *)temp_processor_name;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		regs = cpuid(0x80000002 + i);
+		name_as_ints[i * 4 + 0] = regs.eax;
+		name_as_ints[i * 4 + 1] = regs.ebx;
+		name_as_ints[i * 4 + 2] = regs.ecx;
+		name_as_ints[i * 4 + 3] = regs.edx;
+	}
+
+	temp_processor_name[48] = 0;
+
+	/* Skip leading spaces. */
+	processor_name_start = temp_processor_name;
+	while (*processor_name_start == ' ')
+		processor_name_start++;
+
+	memset(processor_name, 0, 49);
+	strcpy(processor_name, processor_name_start);
+}
+
 int print_cpuinfo(void)
 {
-	printf("CPU:   %s\n", cpu_has_64bit() ? "x86_64" : "x86");
+	char processor_name[49];
+
+	printf("CPU: %s, vendor %s, device %xh\n", cpu_has_64bit() ? "x86_64" : "x86",
+		cpu_vendor_name(gd->arch.x86_vendor), gd->arch.x86_device);
+	printf("CPU: family %02xh, model %02xh, stepping %02xh\n",
+		gd->arch.x86, gd->arch.x86_model, gd->arch.x86_mask);
+	fill_processor_name(processor_name);
+	printf("CPU: %s\n", processor_name);
 
 	return 0;
 }
diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h
index 6c6774a..6cd9f5b 100644
--- a/arch/x86/include/asm/cpu.h
+++ b/arch/x86/include/asm/cpu.h
@@ -1,12 +1,154 @@
 /*
  * Copyright (c) 2014 The Chromium OS Authors.
  *
+ * Part of this file is adapted from coreboot
+ * src/arch/x86/include/arch/cpu.h and
+ * src/arch/x86/lib/cpu.c
+ *
  * SPDX-License-Identifier:	GPL-2.0+
  */
 
 #ifndef __X86_CPU_H
 #define __X86_CPU_H
 
+#define X86_VENDOR_INVALID    0
+#define X86_VENDOR_INTEL      1
+#define X86_VENDOR_CYRIX      2
+#define X86_VENDOR_AMD        3
+#define X86_VENDOR_UMC        4
+#define X86_VENDOR_NEXGEN     5
+#define X86_VENDOR_CENTAUR    6
+#define X86_VENDOR_RISE       7
+#define X86_VENDOR_TRANSMETA  8
+#define X86_VENDOR_NSC        9
+#define X86_VENDOR_SIS       10
+#define X86_VENDOR_ANY     0xfe
+#define X86_VENDOR_UNKNOWN 0xff
+
+struct cpuid_result {
+	uint32_t eax;
+	uint32_t ebx;
+	uint32_t ecx;
+	uint32_t edx;
+};
+
+/*
+ * Generic CPUID function
+ */
+static inline struct cpuid_result cpuid(int op)
+{
+	struct cpuid_result result;
+	asm volatile(
+		"mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%ebx, %%esi;"
+		"mov %%edi, %%ebx;"
+		: "=a" (result.eax),
+		  "=S" (result.ebx),
+		  "=c" (result.ecx),
+		  "=d" (result.edx)
+		: "0" (op)
+		: "edi");
+	return result;
+}
+
+/*
+ * Generic Extended CPUID function
+ */
+static inline struct cpuid_result cpuid_ext(int op, unsigned ecx)
+{
+	struct cpuid_result result;
+	asm volatile(
+		"mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%ebx, %%esi;"
+		"mov %%edi, %%ebx;"
+		: "=a" (result.eax),
+		  "=S" (result.ebx),
+		  "=c" (result.ecx),
+		  "=d" (result.edx)
+		: "0" (op), "2" (ecx)
+		: "edi");
+	return result;
+}
+
+/*
+ * CPUID functions returning a single datum
+ */
+static inline unsigned int cpuid_eax(unsigned int op)
+{
+	unsigned int eax;
+
+	__asm__("mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%edi, %%ebx;"
+		: "=a" (eax)
+		: "0" (op)
+		: "ecx", "edx", "edi");
+	return eax;
+}
+
+static inline unsigned int cpuid_ebx(unsigned int op)
+{
+	unsigned int eax, ebx;
+
+	__asm__("mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%ebx, %%esi;"
+		"mov %%edi, %%ebx;"
+		: "=a" (eax), "=S" (ebx)
+		: "0" (op)
+		: "ecx", "edx", "edi");
+	return ebx;
+}
+
+static inline unsigned int cpuid_ecx(unsigned int op)
+{
+	unsigned int eax, ecx;
+
+	__asm__("mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%edi, %%ebx;"
+		: "=a" (eax), "=c" (ecx)
+		: "0" (op)
+		: "edx", "edi");
+	return ecx;
+}
+
+static inline unsigned int cpuid_edx(unsigned int op)
+{
+	unsigned int eax, edx;
+
+	__asm__("mov %%ebx, %%edi;"
+		"cpuid;"
+		"mov %%edi, %%ebx;"
+		: "=a" (eax), "=d" (edx)
+		: "0" (op)
+		: "ecx", "edi");
+	return edx;
+}
+
+/* Standard macro to see if a specific flag is changeable */
+static inline int flag_is_changeable_p(uint32_t flag)
+{
+	uint32_t f1, f2;
+
+	asm(
+		"pushfl\n\t"
+		"pushfl\n\t"
+		"popl %0\n\t"
+		"movl %0,%1\n\t"
+		"xorl %2,%0\n\t"
+		"pushl %0\n\t"
+		"popfl\n\t"
+		"pushfl\n\t"
+		"popl %0\n\t"
+		"popfl\n\t"
+		: "=&r" (f1), "=&r" (f2)
+		: "ir" (flag));
+	return ((f1^f2) & flag) != 0;
+}
+
  /**
  * cpu_enable_paging_pae() - Enable PAE-paging
  *
diff --git a/arch/x86/include/asm/global_data.h b/arch/x86/include/asm/global_data.h
index 3e8e2cd..721e7dc 100644
--- a/arch/x86/include/asm/global_data.h
+++ b/arch/x86/include/asm/global_data.h
@@ -13,6 +13,11 @@
 /* Architecture-specific global data */
 struct arch_global_data {
 	struct global_data *gd_addr;		/* Location of Global Data */
+	uint8_t  x86;			/* CPU family */
+	uint8_t  x86_vendor;		/* CPU vendor */
+	uint8_t  x86_model;
+	uint8_t  x86_mask;
+	uint32_t x86_device;
 	uint64_t tsc_base;		/* Initial value returned by rdtsc() */
 	uint32_t tsc_base_kclocks;	/* Initial tsc as a kclocks value */
 	uint32_t tsc_prev;		/* For show_boot_progress() */
-- 
1.8.2.1



More information about the U-Boot mailing list