expose host CPU features to guests: Take 2

Dan Kenigsberg
2007-09-10T07:40:05Z

As with Take 1 of this patch, its purpose is to expose host CPU features to
guests. It proved rather helpful to KVM in various benchmarks, and it should
similarly speed kqemu up. Note that it does not extend the set of emulated
opcodes, only exposes what's supported by the host CPU.

I changed the patch according to the comments on these mailing lists. Thanks
for your suggestions. Changes since "take 1" are:
- Use the new -cpu option to specify cpu features.
- Read host cpuid directly, instead of parsing /proc/cpuinfo. This approach is
less Linux-centric and should work with other OSs too. It means however, that
the cpuid is not checked for old CPU bugs. In the future, qemu should
negotiate the actually-supported features with the virutalizing kernel
module.

Index: hw/pc.c
===================================================================
RCS file: /sources/qemu/qemu/hw/pc.c,v
retrieving revision 1.83
diff -u -r1.83 pc.c
--- hw/pc.c 26 Aug 2007 17:51:39 -0000 1.83
+++ hw/pc.c 10 Sep 2007 06:54:00 -0000
@@ -666,7 +666,7 @@
DisplayState *ds, const char **fd_filename, int snapshot,
const char *kernel_filename, const char *kernel_cmdline,
const char *initrd_filename,
- int pci_enabled)
+ int pci_enabled, char *cpu_model)
{
char buf[1024];
int ret, linux_boot, i;
@@ -683,7 +683,9 @@

/* init CPUs */
for(i = 0; i < smp_cpus; i++) {
+ void apply_i386_cpu_model(CPUX86State *env, char *cpu_model);
env = cpu_init();
+ apply_i386_cpu_model(env, cpu_model);
if (i != 0)
env->hflags |= HF_HALTED_MASK;
if (smp_cpus > 1) {
@@ -948,7 +950,7 @@
pc_init1(ram_size, vga_ram_size, boot_device,
ds, fd_filename, snapshot,
kernel_filename, kernel_cmdline,
- initrd_filename, 1);
+ initrd_filename, 1, cpu_model);
}

static void pc_init_isa(int ram_size, int vga_ram_size, int boot_device,
@@ -962,7 +964,7 @@
pc_init1(ram_size, vga_ram_size, boot_device,
ds, fd_filename, snapshot,
kernel_filename, kernel_cmdline,
- initrd_filename, 0);
+ initrd_filename, 0, cpu_model);
}

QEMUMachine pc_machine = {
Index: target-i386/cpu.h
===================================================================
RCS file: /sources/qemu/qemu/target-i386/cpu.h,v
retrieving revision 1.45
diff -u -r1.45 cpu.h
--- target-i386/cpu.h 11 Jul 2007 22:48:58 -0000 1.45
+++ target-i386/cpu.h 10 Sep 2007 06:54:00 -0000
@@ -267,21 +267,44 @@
#define CPUID_CMOV (1 << 15)
#define CPUID_PAT (1 << 16)
#define CPUID_PSE36 (1 << 17)
+#define CPUID_PN (1 << 18)
#define CPUID_CLFLUSH (1 << 19)
-/* ... */
+#define CPUID_DTS (1 << 21)
+#define CPUID_ACPI (1 << 22)
#define CPUID_MMX (1 << 23)
#define CPUID_FXSR (1 << 24)
#define CPUID_SSE (1 << 25)
#define CPUID_SSE2 (1 << 26)
+#define CPUID_SS (1 << 27)
+#define CPUID_HT (1 << 28)
+#define CPUID_TM (1 << 29)
+#define CPUID_IA64 (1 << 30)
+#define CPUID_PBE (1 << 31)

#define CPUID_EXT_SSE3 (1 << 0)
#define CPUID_EXT_MONITOR (1 << 3)
+#define CPUID_EXT_DSCPL (1 << 4)
+#define CPUID_EXT_VMX (1 << 5)
+#define CPUID_EXT_SMX (1 << 6)
+#define CPUID_EXT_EST (1 << 7)
+#define CPUID_EXT_TM2 (1 << 8)
+#define CPUID_EXT_SSSE3 (1 << 9)
+#define CPUID_EXT_CID (1 << 10)
#define CPUID_EXT_CX16 (1 << 13)
+#define CPUID_EXT_XTPR (1 << 14)
+#define CPUID_EXT_DCA (1 << 17)
+#define CPUID_EXT_POPCNT (1 << 22)

#define CPUID_EXT2_SYSCALL (1 << 11)
+#define CPUID_EXT2_MP (1 << 19)
#define CPUID_EXT2_NX (1 << 20)
+#define CPUID_EXT2_MMXEXT (1 << 22)
#define CPUID_EXT2_FFXSR (1 << 25)
+#define CPUID_EXT2_PDPE1GB (1 << 26)
+#define CPUID_EXT2_RDTSCP (1 << 27)
#define CPUID_EXT2_LM (1 << 29)
+#define CPUID_EXT2_3DNOWEXT (1 << 30)
+#define CPUID_EXT2_3DNOW (1 << 31)

#define EXCP00_DIVZ 0
#define EXCP01_SSTP 1
Index: target-i386/helper2.c
===================================================================
RCS file: /sources/qemu/qemu/target-i386/helper2.c,v
retrieving revision 1.48
diff -u -r1.48 helper2.c
--- target-i386/helper2.c 31 Jul 2007 23:09:18 -0000 1.48
+++ target-i386/helper2.c 10 Sep 2007 06:54:00 -0000
@@ -45,40 +45,8 @@
#endif
#endif /* USE_CODE_COPY */

-CPUX86State *cpu_x86_init(void)
+static int set_guest_cpu_basic(CPUX86State *env)
{
- CPUX86State *env;
- static int inited;
-
- env = qemu_mallocz(sizeof(CPUX86State));
- if (!env)
- return NULL;
- cpu_exec_init(env);
-
- /* init various static tables */
- if (!inited) {
- inited = 1;
- optimize_flags_init();
- }
-#ifdef USE_CODE_COPY
- /* testing code for code copy case */
- {
- struct modify_ldt_ldt_s ldt;
-
- ldt.entry_number = 1;
- ldt.base_addr = (unsigned long)env;
- ldt.limit = (sizeof(CPUState) + 0xfff) >> 12;
- ldt.seg_32bit = 1;
- ldt.contents = MODIFY_LDT_CONTENTS_DATA;
- ldt.read_exec_only = 0;
- ldt.limit_in_pages = 1;
- ldt.seg_not_present = 0;
- ldt.useable = 1;
- modify_ldt(1, &ldt, sizeof(ldt)); /* write ldt entry */
-
- asm volatile ("movl %0, %%fs" : : "r" ((1 << 3) | 7));
- }
-#endif
{
int family, model, stepping;
#ifdef TARGET_X86_64
@@ -114,19 +82,7 @@
env->cpuid_ext_features = CPUID_EXT_SSE3;
env->cpuid_features |= CPUID_FXSR | CPUID_MMX | CPUID_SSE | CPUID_SSE2 | CPUID_PAE | CPUID_SEP;
env->cpuid_features |= CPUID_APIC;
- env->cpuid_xlevel = 0;
- {
- const char *model_id = "QEMU Virtual CPU version " QEMU_VERSION;
- int c, len, i;
- len = strlen(model_id);
- for(i = 0; i < 48; i++) {
- if (i >= len)
- c = '\0';
- else
- c = model_id[i];
- env->cpuid_model[i >> 2] |= c << (8 * (i & 3));
- }
- }
+ env->cpuid_xlevel = 0x80000006;
#ifdef TARGET_X86_64
/* currently not enabled for std i386 because not fully tested */
env->cpuid_ext2_features = (env->cpuid_features & 0x0183F3FF);
@@ -139,6 +95,239 @@
env->cpuid_features |= CPUID_PSE36;
#endif
}
+}
+
+static int host_cpuid (uint32_t function, uint32_t *ax,uint32_t *bx, uint32_t *cx, uint32_t *dx)
+{
+ asm("cpuid"
+ : "=a" (*ax),
+ "=b" (*bx),
+ "=c" (*cx),
+ "=d" (*dx)
+ : "a" (function));
+}
+
+static int set_guest_cpu_hostlike(CPUX86State *env) {
+ uint32_t ax, bx, cx, dx;
+ host_cpuid(0, &ax, &bx, &cx, &dx);
+ env->cpuid_level = 2; /* host real level is ax */
+ env->cpuid_vendor1 = bx;
+ env->cpuid_vendor2 = dx;
+ env->cpuid_vendor3 = cx;
+
+ host_cpuid(1, &ax, &bx, &cx, &dx);
+ env->cpuid_version = ax;
+ env->cpuid_features = dx & ~CPUID_ACPI; /* acpi panics linux guests */
+ env->cpuid_ext_features = cx & ~CPUID_EXT_VMX; /* vmx is not recursive */
+
+ /* cpuid(0x80000000, &ax, &bx, &cx, &dx); */
+ env->cpuid_xlevel = 0x80000008;
+
+ host_cpuid(0x80000001, &ax, &bx, &cx, &dx);
+ env->cpuid_ext2_features = dx;
+
+ return 0;
+}
+
+/* x86_cap_flags taken from Linux's arch/i386/kernel/cpu/proc.c */
+static const char * const x86_cap_flags[] = {
+ /* Intel-defined */
+ "fpu", "vme", "de", "pse", "tsc", "msr", "pae", "mce",
+ "cx8", "apic", NULL, "sep", "mtrr", "pge", "mca", "cmov",
+ "pat", "pse36", "pn", "clflush", NULL, "dts", "acpi", "mmx",
+ "fxsr", "sse", "sse2", "ss", "ht", "tm", "ia64", "pbe",
+
+ /* AMD-defined */
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, "syscall", NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, "mp", "nx", NULL, "mmxext", NULL,
+ NULL, "fxsr_opt", "pdpe1gb", "rdtscp", NULL, "lm", "3dnowext", "3dnow",
+
+ /* Transmeta-defined */
+ "recovery", "longrun", NULL, "lrti", NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+
+ /* Other (Linux-defined) */
+ "cxmmx", "k6_mtrr", "cyrix_arr", "centaur_mcr",
+ NULL, NULL, NULL, NULL,
+ "constant_tsc", "up", NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+
+ /* Intel-defined (#2) */
+ "pni", NULL, NULL, "monitor", "ds_cpl", "vmx", "smx", "est",
+ "tm2", "ssse3", "cid", NULL, NULL, "cx16", "xtpr", NULL,
+ NULL, NULL, "dca", NULL, NULL, NULL, NULL, "popcnt",
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+
+ /* VIA/Cyrix/Centaur-defined */
+ NULL, NULL, "rng", "rng_en", NULL, NULL, "ace", "ace_en",
+ "ace2", "ace2_en", "phe", "phe_en", "pmm", "pmm_en", NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+
+ /* AMD-defined (#2) */
+ "lahf_lm", "cmp_legacy", "svm", "extapic", "cr8legacy", "abm",
+ "sse4a", "misalignsse",
+ "3dnowprefetch", "osvw", "ibs", NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+void add_flagname_to_bitmaps(char *flagname, int *features, int *ext_features, int *ext2_features)
+{
+ int i;
+ for ( i = 0 ; i < 32 ; i++ )
+ if (x86_cap_flags[i] && !strcmp (flagname, x86_cap_flags[i])) {
+ *features |= 1 << i;
+ return;
+ }
+ for ( i = 0 ; i < 32 ; i++ )
+ if (x86_cap_flags[32*4+i] && !strcmp (flagname, x86_cap_flags[32*4+i])) {
+ *ext_features |= 1 << i;
+ return;
+ }
+ for ( i = 0 ; i < 32 ; i++ )
+ if (x86_cap_flags[32*1+i] && !strcmp (flagname, x86_cap_flags[32*1+i])) {
+ *ext2_features |= 1 << i;
+ return;
+ }
+ /* Silently ignore Linux-defined features */
+ for ( i = 0 ; i < 32 ; i++ )
+ if (x86_cap_flags[32*3+i] && !strcmp (flagname, x86_cap_flags[32*3+i])) {
+ return;
+ }
+ fprintf(stderr, "CPU feature %s not found\n", flagname);
+}
+
+
+void apply_i386_cpu_model(CPUX86State *env, char *cpu_model)
+{
+ int plus_features = 0, plus_ext_features = 0, plus_ext2_features = 0;
+ int minus_features = 0, minus_ext_features = 0, minus_ext2_features = 0;
+ int family = -1, model = -1, stepping = -1;
+
+ char *s, *featurestr;
+
+ if (!cpu_model)
+ cpu_model = "host";
+
+ s = strdup(cpu_model);
+
+ strtok(s, ",");
+ if (!strcmp(s, "basic"))
+ set_guest_cpu_basic(env);
+ else if (!strcmp(s, "host"))
+ set_guest_cpu_hostlike(env);
+ else {
+ printf("Available CPUs:\n");
+ printf("(basic|host)[,(+feature|-feature|feature=xyz)]...\n");
+ exit(1);
+ }
+
+ featurestr = strtok(NULL, ",");
+ while (featurestr) {
+ char *val;
+ if (featurestr[0] == '+') {
+ add_flagname_to_bitmaps(featurestr + 1, &plus_features, &plus_ext_features, &plus_ext2_features);
+ }
+ else if (featurestr[0] == '-') {
+ add_flagname_to_bitmaps(featurestr + 1, &minus_features, &minus_ext_features, &minus_ext2_features);
+ }
+ else if ((val = strchr(featurestr, '='))) {
+ *val = 0; val++;
+ if (!strcmp(featurestr, "family")) {
+ family = atoi(val);
+ if (family <= 0) {
+ fprintf(stderr, "bad numerical value %s\n", val);
+ exit(1);
+ }
+ env->cpuid_version &= 0xFF;
+ env->cpuid_version |= (family << 8);
+ } else if (!strcmp(featurestr, "model")) {
+ model = atoi(val);
+ if (model <= 0) {
+ fprintf(stderr, "bad numerical value %s\n", val);
+ exit(1);
+ }
+ env->cpuid_version &= 0xFFFFFF0F;
+ env->cpuid_version |= (model << 4);
+ } else if (!strcmp(featurestr, "stepping")) {
+ stepping = atoi(val);
+ if (stepping <= 0) {
+ fprintf(stderr, "bad numerical value %s\n", val);
+ exit(1);
+ }
+ env->cpuid_version &= 0xFFFFFFF0;
+ env->cpuid_version |= stepping;
+ } else {
+ fprintf(stderr, "unknown feature %s\n", featurestr);
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "feature string `%s' not in format (+feature|-feature|feature=xyz)\n", featurestr);
+ exit(1);
+ }
+ featurestr = strtok(NULL, ",");
+ }
+ free(s);
+ env->cpuid_features |= plus_features;
+ env->cpuid_ext_features |= plus_ext_features;
+ env->cpuid_ext2_features |= plus_ext2_features;
+ env->cpuid_features &= ~minus_features;
+ env->cpuid_ext_features &= ~minus_ext_features;
+ env->cpuid_ext2_features &= ~minus_ext2_features;
+
+ {
+ const char *model_id = "QEMU Virtual CPU version " QEMU_VERSION;
+ int c, len, i;
+ len = strlen(model_id);
+ for(i = 0; i < 48; i++) {
+ if (i >= len)
+ c = '\0';
+ else
+ c = model_id[i];
+ env->cpuid_model[i >> 2] |= c << (8 * (i & 3));
+ }
+ }
+}
+
+CPUX86State *cpu_x86_init(void)
+{
+ CPUX86State *env;
+ static int inited;
+
+ env = qemu_mallocz(sizeof(CPUX86State));
+ if (!env)
+ return NULL;
+ cpu_exec_init(env);
+
+ /* init various static tables */
+ if (!inited) {
+ inited = 1;
+ optimize_flags_init();
+ }
+#ifdef USE_CODE_COPY
+ /* testing code for code copy case */
+ {
+ struct modify_ldt_ldt_s ldt;
+
+ ldt.entry_number = 1;
+ ldt.base_addr = (unsigned long)env;
+ ldt.limit = (sizeof(CPUState) + 0xfff) >> 12;
+ ldt.seg_32bit = 1;
+ ldt.contents = MODIFY_LDT_CONTENTS_DATA;
+ ldt.read_exec_only = 0;
+ ldt.limit_in_pages = 1;
+ ldt.seg_not_present = 0;
+ ldt.useable = 1;
+ modify_ldt(1, &ldt, sizeof(ldt)); /* write ldt entry */
+
+ asm volatile ("movl %0, %%fs" : : "r" ((1 << 3) | 7));
+ }
+#endif
cpu_reset(env);
#ifdef USE_KQEMU
kqemu_init(env);