This is an example script that can be used to help generate a config file that will reproduce a given CPU model from QEMU. The generated config file can be loaded using "-readconfig" to make QEMU create CPUs that will look exactly like the one used when cpu-model-dump was run.
A cpu-model-dump-selftest script is also provided, to help ensure that the output of cpu-model-dump will produce the same config when run under cpu-model-dump again. Signed-off-by: Eduardo Habkost <ehabk...@redhat.com> --- Changes v1 -> v2: * Use "cpuid-" prefix instead of "feat-" * Exit earlier if QEMU fails * Exit code of the script will match QEMU or diff exit code --- scripts/x86-cpu-model-dump | 221 ++++++++++++++++++++++++++++++++++++ scripts/x86-cpu-model-dump-selftest | 41 +++++++ 2 files changed, 262 insertions(+) create mode 100755 scripts/x86-cpu-model-dump create mode 100755 scripts/x86-cpu-model-dump-selftest diff --git a/scripts/x86-cpu-model-dump b/scripts/x86-cpu-model-dump new file mode 100755 index 0000000..38812ab --- /dev/null +++ b/scripts/x86-cpu-model-dump @@ -0,0 +1,221 @@ +#!/usr/bin/env python2.7 +# +# Script to dump CPU model information as a QEMU config file that can be loaded +# using -readconfig +# +# Author: Eduardo Habkost <ehabk...@redhat.com> +# +# Copyright (c) 2015 Red Hat Inc. +# +# 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. + + +import sys, os, signal, tempfile, re +import xml.etree.ElementTree + +# Allow us to load the qmp/qmp.py module: +sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), 'qmp')) +import qmp + +CPU_PATH = '/machine/icc-bridge/icc/child[0]' +RE_PROPS = re.compile('x?level2?|vendor|family|model|stepping|cpuid-.*|model-id') +CPU_MAP = '/usr/share/libvirt/cpu_map.xml' + +# features that may not be on cpu_map.xml: +KNOWN_FEAT_NAMES = [ + (0x40000001,0,'eax', [ + "kvmclock", "kvm-nopiodelay", "kvm-mmu", "kvmclock", + "kvm-asyncpf", "kvm-steal-time", "kvm-pv-eoi", "kvm-pv-unhalt", + None, None, None, None, + None, None, None, None, + None, None, None, None, + None, None, None, None, + "kvmclock-stable-bit", None, None, None, + None, None, None, None, + ]), + (0xd,1,'eax',[ + "xsaveopt", "xsavec", "xgetbv1", "xsaves", + ]), + # CPU feature aliases don't have properties, add some special feature + # names telling the script to ignore them: + (0x80000001,0,'edx',[ + "fpu-ALIAS", "vme-ALIAS", "de-ALIAS", "pse-ALIAS", + "tsc-ALIAS", "msr-ALIAS", "pae-ALIAS", "mce-ALIAS", + "cx8-ALIAS", "apic-ALIAS", None, None, + "mtrr-ALIAS", "pge-ALIAS", "mca-ALIAS", "cmov-ALIAS", + "pat-ALIAS", "pse36-ALIAS", None, None, + None, None, None, "mmx-ALIAS", + "fxsr-ALIAS", None, None, None, + None, None, None, None, + ]) +] + +def value_to_string(ptype, v): + """Convert property value to string parseable by -global""" + if ptype == "bool": + return v and "on" or "off" + elif ptype == 'string': + return v + elif ptype.startswith('int') or ptype.startswith('uint'): + return str(v) + else: + raise Exception("Unsupported property type: %s", ptype) + +def load_feat_names(cpu_map): + """Load feature names from libvirt cpu_map.xml""" + cpumap = xml.etree.ElementTree.parse(cpu_map) + feat_names = {} + + for function,index,reg,names in KNOWN_FEAT_NAMES: + for bitnr,name in enumerate(names): + if name: + feat_names[(function,index,reg,bitnr)] = name + + for f in cpumap.getroot().findall("./arch[@name='x86']/feature"): + fname = f.attrib['name'] + for cpuid in f.findall('cpuid'): + function=int(cpuid.attrib['function'], 0) + index = 0 + for reg in 'abcd': + regname = 'e%sx' % (reg) + if regname in cpuid.attrib: + v = int(cpuid.attrib[regname], 0) + for bitnr in range(32): + bitval = (1 << bitnr) + if v & bitval: + feat_names[(function,index,regname,bitnr)] = fname + + return feat_names + +def dump_cpu_data(qmp, cpu_path): + + feat_names = load_feat_names(CPU_MAP) + + props = qmp.command('qom-list', path=cpu_path) + props = sorted([(prop['name'], prop['type']) for prop in props]) + + propdict = {} + for pname,ptype in props: + if RE_PROPS.match(pname): + value = qmp.command('qom-get', path=cpu_path, property=pname) + propdict[pname] = value_to_string(ptype, value) + #print >>sys.stderr, pname, propdict[pname] + + # sanity-check feature-words and fix filtered-features: + for prop in ('feature-words', 'filtered-features'): + reply = qmp.command('qom-get', path=cpu_path, property=prop) + for fw in reply: + function = fw['cpuid-input-eax'] + index = fw.get('cpuid-input-ecx', 0) + regname = fw['cpuid-register'].lower() + value = fw['features'] + for bitnr in range(32): + bitval = (1 << bitnr) + is_set = (value & bitval) != 0 + key = (function,index,regname,bitnr) + keystr = "0x%x,0x%x,%s,%d" % (function, index, regname, bitnr) + feat_name = feat_names.get(key) + + if feat_name is None: + if is_set: + raise Exception("Unknown feature is set: %s" % (keystr)) + else: + continue + + # special case for alias bits: ignore them + if feat_name.endswith('-ALIAS'): + continue + + pname = 'cpuid-%s' % (feat_name.replace('_', '-')) + if not propdict.has_key(pname): + if is_set: + raise Exception("Enabled feature with no property: %s" % pname) + else: + continue + + # feature-word bits must match property: + if prop == 'feature-words': + propvalue = value_to_string('bool', is_set) + assert propdict[pname] == propvalue + # bits set on filtered-features needs property fixup: + elif prop == 'filtered-features' and is_set: + assert propdict[pname] == 'off' + propdict[pname] = 'on' + + for pname in sorted(propdict.keys()): + pvalue = propdict[pname] + print '[global]' + print 'driver = "cpu"' + print 'property = "%s"' % (pname) + print 'value = "%s"' % (pvalue) + print '' + +def main(argv): + args = argv[1:] + if len(args) < 1: + print >>sys.stderr, "Usage: %s <qemu> [<arguments>...]" % (argv[0]) + return 1 + + qemu = args.pop(0) + + sockdir = tempfile.mkdtemp() + sockpath = os.path.join(sockdir, 'monitor.sock') + pidfile = os.path.join(sockdir, 'pidfile') + + try: + qemu_cmd = [qemu] + qemu_cmd.extend(args) + qemu_cmd.append('-chardev') + qemu_cmd.append('socket,id=qmp0,path=%s,server,nowait' % (sockpath)) + qemu_cmd.append('-qmp') + qemu_cmd.append('chardev:qmp0') + qemu_cmd.append('-daemonize') + qemu_cmd.append('-pidfile') + qemu_cmd.append(pidfile) + + ret = os.spawnvp(os.P_WAIT, qemu, qemu_cmd) + if ret != 0: + print >>sys.stderr, "Failed to start QEMU" + return 1 + + srv = qmp.QEMUMonitorProtocol(sockpath) + srv.connect() + + dump_cpu_data(srv, CPU_PATH) + finally: + try: + pid = int(open(pidfile, 'r').read()) + #print >>sys.stderr, 'Killing QEMU, pid: %d' % (pid) + os.kill(pid, signal.SIGTERM) + os.waitpid(pid, 0) + except: + pass + try: + os.unlink(pidfile) + except: + pass + try: + os.unlink(sockpath) + except: + pass + os.rmdir(sockdir) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/scripts/x86-cpu-model-dump-selftest b/scripts/x86-cpu-model-dump-selftest new file mode 100755 index 0000000..3c62bca --- /dev/null +++ b/scripts/x86-cpu-model-dump-selftest @@ -0,0 +1,41 @@ +#!/bin/bash +# self-test script for cpu-model-dump: check if the generated config file +# will make the script generate a similar config file +# +# Author: Eduardo Habkost <ehabk...@redhat.com> +# +# Copyright (c) 2015 Red Hat Inc. +# +# 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. + +DUMP_SCRIPT="$(dirname $0)/x86-cpu-model-dump" + +QEMU="$1" +shift + +dump1="$(mktemp)" +dump2="$(mktemp)" + +"$DUMP_SCRIPT" "$QEMU" "$@" > "$dump1" && \ + "$DUMP_SCRIPT" "$QEMU" -readconfig "$dump1" > "$dump2" && \ + diff -u "$dump1" "$dump2" +E="$?" + +rm -f "$dump1" "$dump2" +exit "$E" -- 2.1.0