feat: vm-native-setup target-machine refit and identity knobs
This commit is contained in:
@@ -65,7 +65,7 @@ cd vfio-native/packaging
|
||||
| Package | What it is |
|
||||
| --- | --- |
|
||||
| `vfio-native` | the three commands, the patches, the ACPI tables, the benchmark, the libvirt hook |
|
||||
| `vfio-native-qemu` | QEMU 11.1.1 with the platform-identity patches, in `/opt/qemu-native` |
|
||||
| `vfio-native-qemu` | QEMU 11.1.2 with the platform-identity patches, in `/opt/qemu-native` |
|
||||
| `vfio-native-kvm-dkms` | the patched KVM modules, rebuilt by DKMS for every installed 7.2.x kernel |
|
||||
|
||||
The modules land in `updates/dkms/`, which modprobe prefers, but nothing reloads them for you.
|
||||
@@ -144,10 +144,18 @@ vm-native-setup -d win11 -p full -c 8 -m 16 -r -u auto -y
|
||||
| `-s` | Secure Boot with enrolled keys, `on` (default) or `off` |
|
||||
| `-u` | USB passthrough: `none`, `auto`, or `vid:pid,...,0000:bb:dd.f` |
|
||||
| `-r` | randomise the hardware identity: serials, MAC, memory module |
|
||||
| `-S` | refit to a real machine's dumped SMBIOS (Method B) |
|
||||
| `-y` | no prompts |
|
||||
|
||||
It backs the domain up first and prints the revert command. Re-running it is safe.
|
||||
|
||||
To present as a specific machine instead of this host, fill the target keys in the
|
||||
domain's `identity.env` (`SYS_PRODUCT`, `BIOS_*`, `CPU_*`, ...): the tool then authors
|
||||
SMBIOS type 0-4/17 as `-smbios type=` field args (Method A), so a new-laptop refit is a
|
||||
few field edits. `-S <dmi-blob>` instead splits a real machine's dumped DMI table into
|
||||
`-smbios file=` blobs for byte-perfect fidelity (Method B); mixing is per structure type.
|
||||
Either wires the ICH9 USB set, disables HPET, and needs `-p full`.
|
||||
|
||||
### 5. Check it worked
|
||||
|
||||
```sh
|
||||
@@ -274,9 +282,9 @@ and ship with a cover letter and a selftest in `patches/kvm/`.
|
||||
either alone leaves it standing. `EXPERIMENTAL-0006` is built into the DKMS package but off by
|
||||
default; it is not upstream material, because it hands the guest raw host CPUID.
|
||||
|
||||
**QEMU** - eight patchsets against v11.1.1: `01-firmware`, `02-disk-identity`, `03-pci-ids`,
|
||||
`04-fw-cfg`, `05-usb-hid`, `06-audio`, and `07-edid` and `08-cpu-misc`, which clear no check by
|
||||
themselves and are carried for other detectors.
|
||||
**QEMU** - ten patchsets against v11.1.2: `01-firmware`, `02-disk-identity`, `03-pci-ids`,
|
||||
`04-fw-cfg`, `05-usb-hid`, `06-audio`, `09-firmware-chain` and `10-smbios`, and `07-edid` and
|
||||
`08-cpu-misc`, which clear no check by themselves and are carried for other detectors.
|
||||
|
||||
---
|
||||
|
||||
@@ -366,7 +374,7 @@ the whole trail, including the two wrong turns.
|
||||
|
||||
```
|
||||
patches/kvm/ five upstream-style kernel patches, a cover letter, plus one experimental
|
||||
patches/qemu/ eight patchsets against QEMU 11.1.1
|
||||
patches/qemu/ ten patchsets against QEMU 11.1.2
|
||||
packaging/ three AUR-shaped packages: tools, patched QEMU, DKMS modules
|
||||
scripts/ setup, verify, GPU, libvirt hook, module install
|
||||
scripts/generate-tables.py raw SMBIOS type 7/26/27/28/29 blobs for -smbios file=
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Three packages:
|
||||
# vfio-native scripts, patches, ACPI tables and the benchmark
|
||||
# vfio-native-kvm-dkms the patched KVM modules, rebuilt by DKMS per kernel
|
||||
# vfio-native-qemu QEMU 11.1.1 with the platform-identity patches, in /opt
|
||||
# vfio-native-qemu QEMU 11.1.2 with the platform-identity patches, in /opt
|
||||
|
||||
pkgname=vfio-native
|
||||
pkgver=1.3.3
|
||||
@@ -37,6 +37,7 @@ package() {
|
||||
install -Dm755 scripts/install-modules.sh "${share}/scripts/install-modules.sh"
|
||||
install -Dm755 scripts/restore-stock-kvm.sh "${share}/scripts/restore-stock-kvm.sh"
|
||||
install -Dm755 scripts/generate-tables.py "${share}/scripts/generate-tables.py"
|
||||
install -Dm755 scripts/split_smbios.py "${share}/scripts/split_smbios.py"
|
||||
install -Dm755 scripts/libvirt-hook-cpuid-passthrough.sh "${share}/scripts/libvirt-hook-cpuid-passthrough.sh"
|
||||
install -Dm755 scripts/cpuid-passthrough-watch "${share}/scripts/cpuid-passthrough-watch"
|
||||
install -Dm644 -t "${share}/acpi" acpi/*.aml acpi/*.dsl
|
||||
|
||||
+184
-30
@@ -11,8 +11,14 @@
|
||||
# -s on | off Secure Boot with enrolled keys (default on)
|
||||
# -u none | auto | <vid:pid,...|0000:bb:dd.f,...> USB passthrough
|
||||
# -r randomize the hardware identity (serials, MAC, memory modules)
|
||||
# -S <dmi-blob> refit to a real machine's dumped SMBIOS (Method B)
|
||||
# -y no prompts
|
||||
#
|
||||
# To present as a specific machine instead of this host, fill the target keys in
|
||||
# the per-domain identity.env (SYS_PRODUCT, BIOS_*, CPU_*, ...): the tool then
|
||||
# authors SMBIOS type 0-4/17 as field args (Method A). -S <dmi-blob> uses a real
|
||||
# machine's dumped DMI table byte-for-byte instead (Method B). Either needs -p full.
|
||||
#
|
||||
# Takes a plain libvirt domain to the tuned, corrected state in one pass:
|
||||
# detects the host CPU layout and maps vCPUs onto real SMT pairs within one
|
||||
# cache domain, keeps the emulator off the vCPU cores, moves the disk to
|
||||
@@ -23,10 +29,10 @@ set -euo pipefail
|
||||
|
||||
# Run bare it is an interview: every choice is asked, with a default. Every flag
|
||||
# answers one question in advance, and -y takes every default.
|
||||
DOM=win11; PROFILE=""; CORES=""; RAMG=""; ASSUME=0; SECBOOT=""; USBSPEC=""; RANDOMIZE=""
|
||||
while getopts "d:p:c:m:s:u:ryh" o; do case $o in
|
||||
DOM=win11; PROFILE=""; CORES=""; RAMG=""; ASSUME=0; SECBOOT=""; USBSPEC=""; RANDOMIZE=""; DMIBLOB=""
|
||||
while getopts "d:p:c:m:s:u:S:ryh" o; do case $o in
|
||||
d) DOM=$OPTARG;; p) PROFILE=$OPTARG;; c) CORES=$OPTARG;;
|
||||
m) RAMG=$OPTARG;; s) SECBOOT=$OPTARG;; u) USBSPEC=$OPTARG;; r) RANDOMIZE=1;; y) ASSUME=1;;
|
||||
m) RAMG=$OPTARG;; s) SECBOOT=$OPTARG;; u) USBSPEC=$OPTARG;; S) DMIBLOB=$OPTARG;; r) RANDOMIZE=1;; y) ASSUME=1;;
|
||||
# print the header comment block, however long it grows
|
||||
h) sed -n '2,/^[^#]/p' "$0" | sed "\$d; s/^# \?//; s|@@SELFNAME@@|$(basename "$0")|g"; exit 0;;
|
||||
esac; done
|
||||
@@ -302,6 +308,24 @@ gen_identity() {
|
||||
echo "MEM_PART='${mem_part:-KF556C40BB-16}'"
|
||||
echo "MEM_SPEED='${mem_speed:-5600}'"
|
||||
echo "MEM_SERIAL='$(rnd 8 'A-F0-9')'"
|
||||
echo ""
|
||||
echo "# Target refit (Method A): uncomment and fill to present as a specific machine"
|
||||
echo "# instead of this host (SYS_UUID becomes the SMBIOS UUID). Needs -p full."
|
||||
echo "#SYS_MANUFACTURER=''; SYS_PRODUCT=''; SYS_VERSION=''; SYS_UUID=''; SYS_SKU=''; SYS_FAMILY=''"
|
||||
echo "#BIOS_VENDOR=''; BIOS_VERSION=''; BIOS_DATE=''; BIOS_RELEASE=''"
|
||||
echo "#BOARD_MANUFACTURER=''; BOARD_PRODUCT=''; BOARD_VERSION=''"
|
||||
echo "#CHASSIS_MANUFACTURER=''; CHASSIS_TYPE='10'"
|
||||
echo "#CPU_MANUFACTURER='Intel(R) Corporation'; CPU_VERSION=''; CPU_SOCKET=''; CPU_ID=''; CPU_MAX_SPEED=''; CPU_CURRENT_SPEED=''"
|
||||
echo "# CPUID identity override, for when the target CPU vendor differs from this host (model-id alone"
|
||||
echo "# leaves lscpu's vendor/family/model as the host's). Set these to the target's values, e.g. a"
|
||||
echo "# Raptor Lake-U Intel target on an AMD host: GenuineIntel, family 6, model 186, stepping 2. CPU_DISABLE_FLAGS"
|
||||
echo "# drops host-only flags that would contradict the vendor (on AMD: svm sse4a 3dnow 3dnowext"
|
||||
echo "# misalignsse cr8legacy osvw). Host-exclusive features of the real target (Intel vmx/hwp/..) cannot"
|
||||
echo "# be synthesised and stay absent."
|
||||
echo "#CPU_VENDOR_ID=''; CPU_FAMILY=''; CPU_MODEL=''; CPU_STEPPING=''; CPU_DISABLE_FLAGS=''"
|
||||
echo "#MEM_LOCATOR='DIMM 0'; NVME_MODEL=''"
|
||||
echo "# SECOND_MAC adds a present-but-dead NIC (isolated, link down, anti-spoofed) at that MAC:"
|
||||
echo "#SECOND_MAC=''"
|
||||
} > "$DATA/identity.env"
|
||||
}
|
||||
mkdir -p "$DATA"
|
||||
@@ -321,6 +345,32 @@ if [ "$RANDOMIZE" = 1 ] || [ ! -f "$DATA/identity.env" ]; then gen_identity; fi
|
||||
# shellcheck disable=SC1091
|
||||
. "$DATA/identity.env"
|
||||
|
||||
# Target identity: present as a specific machine instead of this host. Method A
|
||||
# (SYS_PRODUCT set in identity.env) authors SMBIOS type 0-4/17 as -smbios type=
|
||||
# field args; Method B (-S <dmi-blob>) splits a real DMI table into -smbios file=
|
||||
# blobs. Both ride on the patched QEMU's 10-smbios, so they need the full level.
|
||||
TARGET_MODE=""
|
||||
if [ -n "$DMIBLOB" ]; then TARGET_MODE=dump
|
||||
elif [ -n "${SYS_PRODUCT:-}" ]; then TARGET_MODE=fields; fi
|
||||
if [ -n "$TARGET_MODE" ]; then
|
||||
[ "$PROFILE" = full ] || { echo "target identity (-S or SYS_PRODUCT) needs -p full"; exit 1; }
|
||||
[ "$TARGET_MODE" != dump ] || [ -f "$DMIBLOB" ] || { echo "no such DMI blob: $DMIBLOB"; exit 1; }
|
||||
# a hand-written target identity.env may set only the keys it cares about
|
||||
for k in SYS_MANUFACTURER SYS_PRODUCT SYS_VERSION SYS_UUID SYS_SKU SYS_FAMILY SYS_SERIAL \
|
||||
BIOS_VENDOR BIOS_VERSION BIOS_DATE BIOS_RELEASE BOARD_MANUFACTURER BOARD_PRODUCT \
|
||||
BOARD_VERSION BOARD_SERIAL CHASSIS_MANUFACTURER CHASSIS_TYPE CHASSIS_SERIAL \
|
||||
CPU_MANUFACTURER CPU_VERSION CPU_SOCKET CPU_ID CPU_MAX_SPEED CPU_CURRENT_SPEED \
|
||||
CPU_VENDOR_ID CPU_FAMILY CPU_MODEL CPU_STEPPING CPU_DISABLE_FLAGS \
|
||||
MEM_MANUFACTURER MEM_PART MEM_SERIAL MEM_SPEED MEM_LOCATOR \
|
||||
NVME_SERIAL NVME_MODEL MAC SECOND_MAC; do
|
||||
declare -n _k="$k"; : "${_k:=}"
|
||||
done; unset -n _k
|
||||
: "${CHASSIS_TYPE:=10}"; : "${MEM_LOCATOR:=DIMM 0}"
|
||||
fi
|
||||
# the shared isolated network a SECOND_MAC dead NIC hangs off (created at apply time)
|
||||
VOID_NET=""
|
||||
[ -n "${SECOND_MAC:-}" ] && [ "$PROFILE" != tuned ] && VOID_NET=vfio-native-void
|
||||
|
||||
# --------------------------------------------------------- USB passthrough ---
|
||||
# Passing a whole controller is cleaner than passing devices one by one: no
|
||||
# emulated hub, no hotplug, nothing in the guest that says "redirected". It is
|
||||
@@ -504,9 +554,14 @@ echo " binary $EMULATOR"
|
||||
echo " memory ${RAMG} GiB"
|
||||
[ -n "$MODEL" ] && [ "$PROFILE" != tuned ] && echo " declares $MODEL"
|
||||
if [ "$PROFILE" != tuned ]; then
|
||||
[ "$CONVERT" = 1 ] && echo " disk emulated NVMe, serial $NVME_SERIAL; virtio devices replaced"
|
||||
if [ -n "$TARGET_MODE" ]; then
|
||||
echo " refit $([ "$TARGET_MODE" = dump ] && echo "dumped SMBIOS from $DMIBLOB (Method B)" || echo "$SYS_PRODUCT (Method A field args)")"
|
||||
[ -n "$CPU_VERSION" ] && echo " CPU $CPU_VERSION; ICH9 USB set; HPET off"
|
||||
fi
|
||||
[ "$CONVERT" = 1 ] && echo " disk emulated NVMe, serial $NVME_SERIAL${NVME_MODEL:+, model $NVME_MODEL}; virtio devices replaced"
|
||||
[ "$SECBOOT" = on ] && echo " firmware Secure Boot, keys enrolled from $NVRAM_TPL"
|
||||
[ "$RANDOMIZE" = 1 ] && echo " identity SMBIOS serials, MAC $MAC and memory module from $DATA/identity.env"
|
||||
[ -n "${SECOND_MAC:-}" ] && echo " 2nd nic dead NIC $SECOND_MAC on isolated $VOID_NET (link down, anti-spoofed)"
|
||||
[ "$PROFILE" = full ] && echo " display none - the guest console goes dark, use SSH or RDP"
|
||||
fi
|
||||
[ -n "$USB_HOSTDEVS" ] && echo " usb $(echo "$USB_HOSTDEVS" | grep -c '<hostdev') hostdev(s): $USBSPEC"
|
||||
@@ -536,6 +591,17 @@ for ((k=0;k<CORES;k++)); do
|
||||
[ "$SMT" = 2 ] && PINS+=" <vcpupin vcpu='$((k*SMT+1))' cpuset='$(sib "${GUEST[$k]}")'/>\n"
|
||||
done
|
||||
|
||||
# The dead NIC's isolated network: no uplink, no host IP, no DHCP, so it reaches
|
||||
# nothing; libvirt names the bridge. Created once, reused by every domain.
|
||||
if [ -n "$VOID_NET" ]; then
|
||||
if ! "${C[@]}" net-info "$VOID_NET" >/dev/null 2>&1; then
|
||||
echo "<network><name>$VOID_NET</name></network>" > "$BACKUP/$VOID_NET.xml"
|
||||
"${C[@]}" net-define "$BACKUP/$VOID_NET.xml" >/dev/null
|
||||
"${C[@]}" net-autostart "$VOID_NET" >/dev/null
|
||||
fi
|
||||
"${C[@]}" net-start "$VOID_NET" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
PROFILE="$PROFILE" VENDOR="$VENDOR" VCPUS="$VCPUS" CORES="$CORES" SMT="$SMT" RAMG="$RAMG" \
|
||||
MODEL="$MODEL" EMULATOR="$EMULATOR" PINS="$PINS" \
|
||||
EMUSET="$(pair "$EMU")" IOCSET="$(pair "$IOC")" NUMANODE="$([ "$NNODES" -gt 1 ] && echo "$GNODE")" \
|
||||
@@ -547,6 +613,7 @@ DMI_SKU="$(dmi product_sku)" DMI_FAMILY="$(dmi product_family)" DMI_BOARD_VENDOR
|
||||
DMI_BOARD="$(dmi board_name)" DMI_BOARD_VERSION="$(dmi board_version)" DMI_CHASSIS_VENDOR="$(dmi chassis_vendor)" \
|
||||
DMI_CHASSIS_VERSION="$(dmi chassis_version)" DMI_BIOS_VENDOR="$(dmi bios_vendor)" DMI_BIOS_VERSION="$(dmi bios_version)" \
|
||||
DMI_BIOS_DATE="$(dmi bios_date)" \
|
||||
TARGET_MODE="$TARGET_MODE" SECOND_MAC="${SECOND_MAC:-}" VOID_NET="$VOID_NET" \
|
||||
python3 - "$BACKUP/$DOM.before-setup.xml" "$BACKUP/$DOM.setup.xml" <<'XMLGEN_END'
|
||||
import io, os, re, sys
|
||||
from xml.sax.saxutils import escape
|
||||
@@ -555,6 +622,7 @@ E = os.environ
|
||||
prof, vcpus, cores, smt = E["PROFILE"], int(E["VCPUS"]), int(E["CORES"]), int(E["SMT"])
|
||||
s = io.open(src, encoding="utf-8").read()
|
||||
conformant = prof != "tuned"
|
||||
target = E.get("TARGET_MODE") or ""
|
||||
|
||||
kib = int(E["RAMG"]) * 1024 * 1024
|
||||
s = re.sub(r"<memory unit='KiB'>\d+</memory>", "<memory unit='KiB'>%d</memory>" % kib, s, count=1)
|
||||
@@ -621,7 +689,7 @@ if "<pm>" not in s:
|
||||
s = s.replace(" <devices>", " <pm>\n <suspend-to-mem enabled='yes'/>\n"
|
||||
" <suspend-to-disk enabled='yes'/>\n </pm>\n <devices>", 1)
|
||||
|
||||
if conformant and E["MODEL"]:
|
||||
if conformant and not target and E["MODEL"]:
|
||||
s = re.sub(r"version=AMD Ryzen [^,]*,", "version=%s," % E["MODEL"], s)
|
||||
|
||||
if conformant and E["CONVERT"] == "1":
|
||||
@@ -669,7 +737,42 @@ if conformant and E["CONVERT"] == "1":
|
||||
if prof == "full":
|
||||
s = re.sub(r"<video>.*?</video>", "<video>\n <model type='none'/>\n </video>", s, flags=re.S)
|
||||
|
||||
if conformant:
|
||||
if conformant and target:
|
||||
# the target's SMBIOS rides in as -smbios qemu args, not libvirt <sysinfo>
|
||||
if E.get("MAC"):
|
||||
s = re.sub(r"<mac address='[^']*'/>", "<mac address='%s'/>" % E["MAC"], s, count=1)
|
||||
s = re.sub(r"\s*<sysinfo type='smbios'>.*?</sysinfo>", "", s, flags=re.S)
|
||||
s = re.sub(r"\s*<smbios mode='[^']*'/>", "", s)
|
||||
# a real laptop has the ICH9 EHCI+UHCI set at 1d, not a bare qemu-xhci
|
||||
s = re.sub(r"\s*<controller type='usb'[^>]*>.*?</controller>", "", s, flags=re.S)
|
||||
s = re.sub(r"\s*<controller type='usb'[^>]*/>", "", s)
|
||||
ich9_usb = (
|
||||
" <controller type='usb' index='0' model='ich9-ehci1'>\n"
|
||||
" <address type='pci' domain='0x0000' bus='0x00' slot='0x1d' function='0x7'/>\n"
|
||||
" </controller>\n"
|
||||
" <controller type='usb' index='0' model='ich9-uhci1'>\n"
|
||||
" <master startport='0'/>\n"
|
||||
" <address type='pci' domain='0x0000' bus='0x00' slot='0x1d' function='0x0' multifunction='on'/>\n"
|
||||
" </controller>\n"
|
||||
" <controller type='usb' index='0' model='ich9-uhci2'>\n"
|
||||
" <master startport='2'/>\n"
|
||||
" <address type='pci' domain='0x0000' bus='0x00' slot='0x1d' function='0x1'/>\n"
|
||||
" </controller>\n"
|
||||
" <controller type='usb' index='0' model='ich9-uhci3'>\n"
|
||||
" <master startport='4'/>\n"
|
||||
" <address type='pci' domain='0x0000' bus='0x00' slot='0x1d' function='0x2'/>\n"
|
||||
" </controller>\n")
|
||||
s = s.replace(" </devices>", ich9_usb + " </devices>", 1)
|
||||
if "name='hpet'" in s:
|
||||
s = re.sub(r"<timer name='hpet'[^>]*/>", "<timer name='hpet' present='no'/>", s, count=1)
|
||||
elif re.search(r"<clock\b[^>]*/>", s):
|
||||
s = re.sub(r"(<clock\b[^>]*)/>",
|
||||
r"\1>\n <timer name='hpet' present='no'/>\n </clock>", s, count=1)
|
||||
else:
|
||||
s = re.sub(r"(<clock\b[^>]*>)",
|
||||
r"\1\n <timer name='hpet' present='no'/>", s, count=1)
|
||||
|
||||
if conformant and not target:
|
||||
if E["RANDOMIZE"] == "1":
|
||||
s = re.sub(r"<mac address='[^']*'/>", "<mac address='%s'/>" % E["MAC"], s, count=1)
|
||||
def ent(name, key):
|
||||
@@ -689,6 +792,7 @@ if conformant:
|
||||
if "<smbios mode=" not in s:
|
||||
s = s.replace(" </os>", " <smbios mode='sysinfo'/>\n </os>", 1)
|
||||
|
||||
if conformant:
|
||||
if E["SECBOOT"] == "on":
|
||||
s = re.sub(r"<os firmware='efi'>", "<os>", s, count=1)
|
||||
s = re.sub(r"\s*<firmware>.*?</firmware>", "", s, flags=re.S)
|
||||
@@ -702,6 +806,17 @@ if conformant:
|
||||
if "<smm state='on'/>" not in s:
|
||||
s = s.replace(" </features>", " <smm state='on'/>\n </features>", 1)
|
||||
|
||||
# SECOND_MAC: a present-but-dead NIC on the isolated void network - shows up in the
|
||||
# guest but reaches nothing, with its own clean-traffic anti-spoof. Replace a prior one.
|
||||
if conformant and E.get("SECOND_MAC") and E.get("VOID_NET"):
|
||||
s = re.sub(r"\s*<interface type='network'>(?:(?!</interface>).)*?<source network='%s'/>(?:(?!</interface>).)*</interface>" % re.escape(E["VOID_NET"]), "", s, flags=re.S)
|
||||
nic = (" <interface type='network'>\n"
|
||||
" <mac address='%s'/>\n" % E["SECOND_MAC"] +
|
||||
" <source network='%s'/>\n" % E["VOID_NET"] +
|
||||
" <model type='igb'/>\n <link state='down'/>\n"
|
||||
" <filterref filter='clean-traffic'/>\n </interface>\n")
|
||||
s = s.replace(" </devices>", nic + " </devices>", 1)
|
||||
|
||||
# USB passthrough: replace what this tool put in before, leave other hostdevs alone
|
||||
s = re.sub(r"\s*<hostdev mode='subsystem' type='(usb|pci)' managed='yes'>(?:(?!</hostdev>).)*<alias name='ua-vfionative-(usb|pci)-[^']*'/>(?:(?!</hostdev>).)*</hostdev>", "", s, flags=re.S)
|
||||
if E["USB_HOSTDEVS"]:
|
||||
@@ -726,22 +841,27 @@ fi
|
||||
# are replaced; anything else already in the block is left alone.
|
||||
QARGS=()
|
||||
if [ "$PROFILE" != tuned ]; then
|
||||
kb() { local s; s=$(cat "/sys/devices/system/cpu/cpu0/cache/index$1/size"); echo "${s%K}"; }
|
||||
SPAN=$(( (CORES + MAXC - 1) / MAXC ))
|
||||
python3 "$SELF/scripts/generate-tables.py" --output-dir "$DATA" \
|
||||
--cache-l1 $(( CORES * ($(kb 0) + $(kb 1)) )) --cache-l2 $(( CORES * $(kb 2) )) \
|
||||
--cache-l3 $(( SPAN * $(kb 3) )) >/dev/null
|
||||
for f in "$DATA"/type*.bin; do QARGS+=("-smbios" "file=$f"); done
|
||||
# A target refit builds its SMBIOS from field args (Method A) or a real dump
|
||||
# (Method B), so it skips the synthesised cache/probe blobs; presenting as this
|
||||
# host keeps them, sized from the declared vCPU cache.
|
||||
if [ -z "$TARGET_MODE" ]; then
|
||||
kb() { local s; s=$(cat "/sys/devices/system/cpu/cpu0/cache/index$1/size"); echo "${s%K}"; }
|
||||
SPAN=$(( (CORES + MAXC - 1) / MAXC ))
|
||||
python3 "$SELF/scripts/generate-tables.py" --output-dir "$DATA" \
|
||||
--cache-l1 $(( CORES * ($(kb 0) + $(kb 1)) )) --cache-l2 $(( CORES * $(kb 2) )) \
|
||||
--cache-l3 $(( SPAN * $(kb 3) )) >/dev/null
|
||||
for f in "$DATA"/type*.bin; do QARGS+=("-smbios" "file=$f"); done
|
||||
fi
|
||||
if [ -d "$SELF/acpi" ]; then
|
||||
cp "$SELF"/acpi/*.aml "$DATA"/
|
||||
for f in "$DATA"/*.aml; do QARGS+=("-acpitable" "file=$f"); done
|
||||
fi
|
||||
# A second -cpu REPLACES the first rather than merging, so it is built from
|
||||
# libvirt's own generated line and never hand-written. It is incompatible
|
||||
# with <hyperv>, which is why the tuned level skips it.
|
||||
# libvirt's own -cpu comes first; a previous run's override sits at the
|
||||
# end, quoted because the brand has spaces, so split like a shell would.
|
||||
if [ -n "$MODEL" ]; then
|
||||
# with <hyperv>, which is why the tuned level skips it. The brand is the
|
||||
# target's CPU for a refit, otherwise a matched host SKU.
|
||||
BRAND="$MODEL"; [ -n "$TARGET_MODE" ] && BRAND="$CPU_VERSION"
|
||||
if [ -n "$BRAND" ]; then
|
||||
python3 - "$BACKUP/$DOM.setup.xml" "$BACKUP/$DOM.nohostdev.xml" <<'STRIP_END'
|
||||
import io, re, sys
|
||||
s = io.open(sys.argv[1], encoding="utf-8").read()
|
||||
@@ -751,26 +871,52 @@ STRIP_END
|
||||
import shlex, sys
|
||||
a = shlex.split(sys.stdin.read())
|
||||
print(a[a.index("-cpu") + 1] if "-cpu" in a else "")')
|
||||
if [ -n "$GEN" ] && [[ "$GEN" != *hv-* ]]; then QARGS+=("-cpu" "$GEN,model-id=$MODEL"); fi
|
||||
# SMBIOS type 4 defaults to manufacturer "QEMU" and the machine name as
|
||||
# the version; a comma inside a QEMU option value is written twice.
|
||||
case "$VENDOR:$FAMILY" in
|
||||
AuthenticAMD:23) SOCK=AM4; CPUVEND="Advanced Micro Devices,, Inc.";;
|
||||
AuthenticAMD:*) SOCK=AM5; CPUVEND="Advanced Micro Devices,, Inc.";;
|
||||
GenuineIntel:*) SOCK=$([ "$MODELNO" -ge 151 ] && echo LGA1700 || echo LGA1200); CPUVEND="Intel(R) Corporation";;
|
||||
*) SOCK=CPU; CPUVEND="$VENDOR";;
|
||||
esac
|
||||
MAXMHZ=$(( $(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq 2>/dev/null || echo 4500000) / 1000 ))
|
||||
QARGS+=("-smbios" "type=4,sock_pfx=$SOCK,manufacturer=$CPUVEND,version=$MODEL,max-speed=$MAXMHZ,current-speed=$MAXMHZ")
|
||||
if [ -n "$GEN" ] && [[ "$GEN" != *hv-* ]]; then
|
||||
CPUARG="$GEN,model-id=$BRAND"
|
||||
[ -n "$CPU_VENDOR_ID" ] && CPUARG="$CPUARG,vendor=$CPU_VENDOR_ID"
|
||||
[ -n "$CPU_FAMILY" ] && CPUARG="$CPUARG,family=$CPU_FAMILY"
|
||||
[ -n "$CPU_MODEL" ] && CPUARG="$CPUARG,model=$CPU_MODEL"
|
||||
[ -n "$CPU_STEPPING" ] && CPUARG="$CPUARG,stepping=$CPU_STEPPING"
|
||||
for _df in $CPU_DISABLE_FLAGS; do CPUARG="$CPUARG,-$_df"; done
|
||||
QARGS+=("-cpu" "$CPUARG")
|
||||
fi
|
||||
fi
|
||||
if [ "$RANDOMIZE" = 1 ]; then
|
||||
QARGS+=("-smbios" "type=17,loc_pfx=DIMM,bank=P0 CHANNEL A,manufacturer=$MEM_MANUFACTURER,part=$MEM_PART,serial=$MEM_SERIAL,speed=$MEM_SPEED")
|
||||
# a comma inside a QEMU option value is written twice
|
||||
esc() { local v="${1//,/,,}"; printf '%s' "$v"; }
|
||||
if [ "$TARGET_MODE" = fields ]; then
|
||||
QARGS+=("-smbios" "type=0,vendor=$(esc "$BIOS_VENDOR"),version=$(esc "$BIOS_VERSION"),date=$(esc "$BIOS_DATE"),release=$(esc "$BIOS_RELEASE"),uefi=on")
|
||||
QARGS+=("-smbios" "type=1,manufacturer=$(esc "$SYS_MANUFACTURER"),product=$(esc "$SYS_PRODUCT"),version=$(esc "$SYS_VERSION"),serial=$(esc "$SYS_SERIAL"),sku=$(esc "$SYS_SKU"),family=$(esc "$SYS_FAMILY")${SYS_UUID:+,uuid=$SYS_UUID}")
|
||||
QARGS+=("-smbios" "type=2,manufacturer=$(esc "$BOARD_MANUFACTURER"),product=$(esc "$BOARD_PRODUCT"),version=$(esc "$BOARD_VERSION"),serial=$(esc "$BOARD_SERIAL")")
|
||||
QARGS+=("-smbios" "type=3,manufacturer=$(esc "$CHASSIS_MANUFACTURER"),serial=$(esc "$CHASSIS_SERIAL"),chassis-type=$CHASSIS_TYPE")
|
||||
QARGS+=("-smbios" "type=4,manufacturer=$(esc "$CPU_MANUFACTURER"),version=$(esc "$CPU_VERSION"),sock_pfx=$(esc "$CPU_SOCKET")${CPU_ID:+,processor-id=$CPU_ID}${CPU_MAX_SPEED:+,max-speed=$CPU_MAX_SPEED}${CPU_CURRENT_SPEED:+,current-speed=$CPU_CURRENT_SPEED}")
|
||||
QARGS+=("-smbios" "type=17,manufacturer=$(esc "$MEM_MANUFACTURER"),part=$(esc "$MEM_PART"),serial=$(esc "$MEM_SERIAL"),speed=$MEM_SPEED,loc_pfx=$(esc "$MEM_LOCATOR")")
|
||||
elif [ "$TARGET_MODE" = dump ]; then
|
||||
rm -rf "$DATA/smbios_real"
|
||||
while read -r f; do [ "${f#\#}" = "$f" ] && QARGS+=("-smbios" "file=$f"); done \
|
||||
< <(python3 "$SELF/scripts/split_smbios.py" "$DMIBLOB" "$DATA/smbios_real")
|
||||
else
|
||||
# present as this host: type 4 from the matched SKU, type 17 with -r
|
||||
if [ -n "$MODEL" ]; then
|
||||
case "$VENDOR:$FAMILY" in
|
||||
AuthenticAMD:23) SOCK=AM4; CPUVEND="Advanced Micro Devices,, Inc.";;
|
||||
AuthenticAMD:*) SOCK=AM5; CPUVEND="Advanced Micro Devices,, Inc.";;
|
||||
GenuineIntel:*) SOCK=$([ "$MODELNO" -ge 151 ] && echo LGA1700 || echo LGA1200); CPUVEND="Intel(R) Corporation";;
|
||||
*) SOCK=CPU; CPUVEND="$VENDOR";;
|
||||
esac
|
||||
MAXMHZ=$(( $(cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq 2>/dev/null || echo 4500000) / 1000 ))
|
||||
QARGS+=("-smbios" "type=4,sock_pfx=$SOCK,manufacturer=$CPUVEND,version=$MODEL,max-speed=$MAXMHZ,current-speed=$MAXMHZ")
|
||||
fi
|
||||
if [ "$RANDOMIZE" = 1 ]; then
|
||||
QARGS+=("-smbios" "type=17,loc_pfx=DIMM,bank=P0 CHANNEL A,manufacturer=$MEM_MANUFACTURER,part=$MEM_PART,serial=$MEM_SERIAL,speed=$MEM_SPEED")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
QARGS_NL="$(printf '%s\n' "${QARGS[@]}")" python3 - "$BACKUP/$DOM.setup.xml" <<'QARGS_END'
|
||||
QARGS_NL="$(printf '%s\n' "${QARGS[@]}")" NVME_MODEL="${NVME_MODEL:-}" \
|
||||
python3 - "$BACKUP/$DOM.setup.xml" <<'QARGS_END'
|
||||
import io, os, re, sys
|
||||
p = sys.argv[1]
|
||||
args = [a for a in os.environ["QARGS_NL"].split("\n") if a]
|
||||
model = os.environ.get("NVME_MODEL", "")
|
||||
s = io.open(p, encoding="utf-8").read()
|
||||
if "xmlns:qemu=" not in s:
|
||||
s = s.replace("<domain type='kvm'>",
|
||||
@@ -796,6 +942,14 @@ if vals or envs:
|
||||
+ "".join(" %s\n" % e for e in envs)
|
||||
+ " </qemu:commandline>\n")
|
||||
s = s.replace("</domain>", block + "</domain>", 1)
|
||||
# libvirt's nvme controller has no model attribute, so the Identify Model Number
|
||||
# rides in as a qemu device override (the serial stays on the controller).
|
||||
if model:
|
||||
s = re.sub(r"\s*<qemu:override>.*?</qemu:override>", "", s, flags=re.S)
|
||||
ovr = (" <qemu:override>\n <qemu:device alias='nvme0'>\n <qemu:frontend>\n"
|
||||
" <qemu:property name='model' type='string' value='%s'/>\n" % model.replace("'", "'") +
|
||||
" </qemu:frontend>\n </qemu:device>\n </qemu:override>\n")
|
||||
s = s.replace("</domain>", ovr + "</domain>", 1)
|
||||
io.open(p, "w", encoding="utf-8").write(s)
|
||||
QARGS_END
|
||||
"${C[@]}" define "$BACKUP/$DOM.setup.xml" >/dev/null
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Split a raw SMBIOS structure table into one file per structure for QEMU.
|
||||
|
||||
Reads a dumped DMI table (/sys/firmware/dmi/tables/DMI on the real machine),
|
||||
writes one `-smbios file=` blob per structure into OUTDIR, and prints the
|
||||
ordered list of paths. A complete set through the type-127 end marker is what
|
||||
lets QEMU build a well-formed entry point from a dump.
|
||||
|
||||
Each structure is a 4-byte header (type, length, handle:u16) + (length-4)
|
||||
formatted bytes + a string set terminated by a double NUL; a structure with no
|
||||
strings is still followed by 0x0000.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def split(data):
|
||||
out, i, n = [], 0, len(data)
|
||||
while i + 4 <= n:
|
||||
stype, slen = data[i], data[i + 1]
|
||||
if slen < 4:
|
||||
break
|
||||
j = i + slen
|
||||
while j + 1 < n and not (data[j] == 0 and data[j + 1] == 0):
|
||||
j += 1
|
||||
j += 2
|
||||
out.append((stype, data[i:j]))
|
||||
i = j
|
||||
if stype == 127:
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
dmi, outdir = sys.argv[1], sys.argv[2]
|
||||
data = open(dmi, "rb").read()
|
||||
os.makedirs(outdir, exist_ok=True)
|
||||
structs = split(data)
|
||||
paths = []
|
||||
for idx, (stype, blob) in enumerate(structs):
|
||||
p = os.path.join(outdir, "sm_%02d_type%d.bin" % (idx, stype))
|
||||
open(p, "wb").write(blob)
|
||||
paths.append(p)
|
||||
print("# %d structures: %s" % (len(structs), ",".join(str(t) for t, _ in structs)))
|
||||
for p in paths:
|
||||
print(p)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user