17 Commits
Author SHA1 Message Date
sandwich 29fb6006f1 chore: release 1.4.1 2026-10-10 03:57:09 +02:00
sandwich 8b77a135ec fix(pci-ids): keep ich9 sata at 1f.2 so ovmf-booted guests find the disk 2026-10-10 03:57:09 +02:00
sandwich d94772ddc2 chore: bump vfio-native package to 1.4.0 for the new tooling 2026-10-10 02:36:35 +02:00
sandwich c2b10a2450 chore: release 1.4.0 with macos-capable qemu series 2026-10-10 02:29:34 +02:00
sandwich 69cf814b34 fix: cpu-isolation hook restores prior allowedcpus instead of all cores 2026-10-10 02:29:34 +02:00
sandwich b1444cc722 feat: vm-native-setup target-machine refit and identity knobs 2026-10-10 02:29:34 +02:00
sandwich d47e1d5fa0 feat: patched qemu 11.1.2 identity series with macos support 2026-10-10 01:11:44 +02:00
sandwich f99ff1400a chore: release 1.3.3 2026-09-06 11:36:25 +02:00
sandwich 4e0df16271 fix: wait for domain to run before cpuid watcher reads its tap 2026-09-06 11:36:25 +02:00
sandwich 0c3425e5af chore: release 1.3.2 2026-09-06 10:07:57 +02:00
sandwich a956ececfd fix: keep cpuid passthrough correct across in-guest reboots 2026-09-06 10:07:57 +02:00
sandwich 966a2a6390 chore: release 1.3.1 2026-09-06 08:56:27 +02:00
sandwich d3062f5e24 fix: stop ly and other tui display managers for single-gpu passthrough 2026-09-06 08:56:27 +02:00
sandwich 1e1078e16a fix: pass only the gpu's iommu group and audio, not sibling psp/usb 2026-09-06 08:56:27 +02:00
sandwich f90745e68e feat: igb nic, jumbo frames and tap offloads on the guest link 2026-09-06 05:49:51 +02:00
sandwich 91fd1c45e1 fix: buffered disk cache so btrfs checksums stay valid 2026-09-06 05:39:31 +02:00
sandwich 52b18427f2 feat(cpuid): automate the passthrough toggle around enabled guests 2026-09-06 02:46:30 +02:00
31 changed files with 1219 additions and 171 deletions
+31 -16
View File
@@ -65,7 +65,7 @@ cd vfio-native/packaging
| Package | What it is | | Package | What it is |
| --- | --- | | --- | --- |
| `vfio-native` | the three commands, the patches, the ACPI tables, the benchmark, the libvirt hook | | `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 | | `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. 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` | | `-s` | Secure Boot with enrolled keys, `on` (default) or `off` |
| `-u` | USB passthrough: `none`, `auto`, or `vid:pid,...,0000:bb:dd.f` | | `-u` | USB passthrough: `none`, `auto`, or `vid:pid,...,0000:bb:dd.f` |
| `-r` | randomise the hardware identity: serials, MAC, memory module | | `-r` | randomise the hardware identity: serials, MAC, memory module |
| `-S` | refit to a real machine's dumped SMBIOS (Method B) |
| `-y` | no prompts | | `-y` | no prompts |
It backs the domain up first and prints the revert command. Re-running it is safe. 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 ### 5. Check it worked
```sh ```sh
@@ -165,25 +173,32 @@ vm-native-verify
`vm-native-verify` also prints whether CPUID passthrough is on, which is the last step. `vm-native-verify` also prints whether CPUID passthrough is on, which is the last step.
### 6. Switch on CPUID passthrough, after every guest boot ### 6. CPUID passthrough, on after boot and off before the next one
The `TIMER` check times the world switch on an intercepted `CPUID`, and the only way to stop The `TIMER` check times the world switch on an intercepted `CPUID`, and the only way to stop
paying it is to not exit. `vm-native-setup` prints the two lines for your declared SKU; on this paying it is to not exit. That switch has to be off while a guest cold-boots - a booting Windows
host they are: enumerates CPUID bits KVM synthesises and hangs if they vanish half way through - and on once it
is up. Let the libvirt hook handle that around a guest:
```sh
sudo vm-native-cpuid enable win11 # off for the cold boot, on once the guest is on the network, off on stop
sudo vm-native-cpuid status # what is enabled, and the live switch state
sudo vm-native-cpuid disable win11 # stop automating this guest
sudo vm-native-cpuid revert # remove the hook and reset the switch
```
Or drive it by hand. `vm-native-setup` prints the two lines for your declared SKU; run them once
the guest is up, never before, and switch it off (`echo N`) before the next boot:
```sh ```sh
echo 'AMD Ryzen 7 7700X 8-Core Processor' | sudo tee /sys/module/kvm_amd/parameters/brand_string echo 'AMD Ryzen 7 7700X 8-Core Processor' | sudo tee /sys/module/kvm_amd/parameters/brand_string
echo Y | sudo tee /sys/module/kvm_amd/parameters/cpuid_passthrough echo Y | sudo tee /sys/module/kvm_amd/parameters/cpuid_passthrough
``` ```
Run them once the guest is up, never before: a booting Windows enumerates CPUID bits KVM The module applies it only to vCPU threads pinned to exactly one host CPU, so on an unpinned guest
synthesises, and hangs if they vanish half way through. Switch it off again (`echo N`) before the it does nothing rather than something wrong. It is one switch and one brand string for the whole
next boot. The module applies it only to vCPU threads pinned to exactly one host CPU, so on an host, so it serves one hardened guest at a time - the hook enforces that and leaves a second guest
unpinned guest it does nothing rather than something wrong. alone.
It is a module parameter, so it is one switch for the whole host: every pinned guest gets it, and
a cold boot of any of them while it is on hits the race. With more than one such guest, switch it
off before any of them boots and on again once they are all up.
For the detection score, run VMAware in the guest **from the console session, not over SSH** - For the detection score, run VMAware in the guest **from the console session, not over SSH** -
OpenSSH lands you in session 0, which is not where an interactive desktop session runs. See OpenSSH lands you in session 0, which is not where an interactive desktop session runs. See
@@ -267,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 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. 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`, **QEMU** - ten patchsets against v11.1.2: `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 `04-fw-cfg`, `05-usb-hid`, `06-audio`, `09-firmware-chain` and `10-smbios`, and `07-edid` and
themselves and are carried for other detectors. `08-cpu-misc`, which clear no check by themselves and are carried for other detectors.
--- ---
@@ -359,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/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 packaging/ three AUR-shaped packages: tools, patched QEMU, DKMS modules
scripts/ setup, verify, GPU, libvirt hook, module install scripts/ setup, verify, GPU, libvirt hook, module install
scripts/generate-tables.py raw SMBIOS type 7/26/27/28/29 blobs for -smbios file= scripts/generate-tables.py raw SMBIOS type 7/26/27/28/29 blobs for -smbios file=
+118 -2
View File
@@ -5,7 +5,7 @@ says *why*, with the source references. Written 2026-09-03 at 2/85, revised 2026
on both VMAware v2.8.1 and VMAware HEAD (commit 95fecc2) - the one survivor is `GPU_CAPABILITIES`, on both VMAware v2.8.1 and VMAware HEAD (commit 95fecc2) - the one survivor is `GPU_CAPABILITIES`,
and VMAware's own verdict is "Running on bare metal", likeliness 20%, confirmation false. and VMAware's own verdict is "Running on bare metal", likeliness 20%, confirmation false.
Source read against: VMAware `src/vmaware.hpp`, QEMU 11.1.1, kernel 7.2.3 `arch/x86/kvm`. Source read against: VMAware `src/vmaware.hpp`, QEMU 11.1.2, kernel 7.2.3 `arch/x86/kvm`.
--- ---
@@ -105,7 +105,9 @@ Links, in order:
5. ~~FACP C2/C3 latencies, vmaware.hpp:9242~~ - `0xfff` -> `0xffe` 5. ~~FACP C2/C3 latencies, vmaware.hpp:9242~~ - `0xfff` -> `0xffe`
6. Debug Port OperationRegion at `0x0402` - already dead on this tree 6. Debug Port OperationRegion at `0x0402` - already dead on this tree
7. HPET register-validation loop - dormant, domain sets `hpet present='no'`. Re-arms if enabled. 7. HPET register-validation loop - dormant, domain sets `hpet present='no'`. Re-arms if enabled.
8. Dummy SATA, DMAR, APIC source overrides - already dead on this tree 8. Dummy SATA, DMAR, APIC source overrides - dead on this tree (SATA at `1f.2`: see the SATA
subsection below - device 31's functions carry no generic `_ADR` node, so the `D0FA`+`0x1F0002`
pair never forms)
Link 3 requires **all four** of `LNKE`, `LNKH`, `GSIE`, `GSIH`, which is QEMU's eight-link plus Link 3 requires **all four** of `LNKE`, `LNKH`, `GSIE`, `GSIH`, which is QEMU's eight-link plus
eight-GSI layout. Real boards have four PCI link devices and no GSI-named ones, so rename the eight-GSI layout. Real boards have four PCI link devices and no GSI-named ones, so rename the
@@ -125,6 +127,120 @@ WAET and never did: `build_waet()` has zero call sites, so its `grep -q build_wa
satisfied by the definition alone and the insert never ran. Guard on a call site, not on a satisfied by the definition alone and the insert never ran. Guard on a call site, not on a
definition. definition.
**The chain grew.** `firmware()` returns on its first hit, so clearing one link only exposes the
next, and current VMAware has added links past the eight above. `09-firmware-chain` clears the
rest, measured to 0 detections on both v2.8.1 and HEAD:
| Link | Fix |
| --- | --- |
| IQST/IQCR + PRR0/PRRI interrupt-query methods | renamed PSTA/PCRS/PRS0/PRSI |
| TPM PPI regions/method TPP2/TPP3/TPFN | renamed PPI2/PPI3/PPIF |
| zeroed `_S5` SLP_TYP package | 2-element package form |
| `ACPI0006` `_HID` in `\_GPE` | declared by full path `\_GPE._HID`; dropping it stops macOS powering off |
| Q35 MMCONFIG reservation device `MCFG` | renamed `PCMM` |
| synthetic GSI link devices, empty `_DIS`/`_SRS` | gave the no-op methods a body |
| PCIe `_OSC` `And(CDW3, 0x1F, ..)` mask | swapped the `And()` operands (commutative) |
| PIRQ link `_PRS` fixed IRQ 5/10/11 | listed the same set as `{11, 10, 5}` |
A q35 guest also needs a named USB controller in the DSDT (the ICH9 EHCI/UHCI set, declared `EHC1`/
`UHC1`), or the release flags "complete absence of USB controller declarations"; that is a domain
choice, not a QEMU patch.
### CLEARED - the CPU-hotplug I/O signature that macOS forced back in (`11-macos-cpu-hotplug`)
macOS references `\_SB.CPUS.Cxxx` from its own CpuPlug SSDT, so the guest must carry QEMU's
`build_cpus_aml` output (the `\_SB.PCI0.PRES` controller plus the `\_SB.CPUS` `Processor` objects)
or it hangs in ACPI namespace init. `01-firmware` had dropped that whole block, which is why macOS
lived in an opt-in patch: the block reintroduces exactly one FIRMWARE tell, so a macOS-capable build
scored 2/85 on v2.8.1 while HEAD stayed clean.
The tell is a single check. v2.8.1 `firmware()` carries `cpu_hotplug_io[] = {0x0B, 0xD8, 0x0C}` and
byte-matches it anywhere in the tables. Those three bytes are `WordData 0x0CD8` - the CPU-hotplug
register port that `build_cpus_aml` writes into the `PRST` `OperationRegion` base
(`ICH9_CPU_HOTPLUG_IO_BASE`). Confirmed by dumping the macOS-build DSDT: the bytes appear only at the
`OperationRegion` operand, never in the `_CRS`. A small-resource `IO` descriptor encodes `0x0CD8` as
`0x47 0x01 0xD8 0x0C 0xD8 0x0C 0x01 0x0C` - the `0xD8 0x0C` runs are preceded by `0x01`/`0x0C`, never
`0x0B` - so the `_CRS` is not what matches and does not need touching. The renamed `_UID` ("Reserved
resources C") already dodges the separate `"CPU Hotplug resources"` string check; it was never the
live tell. HEAD has no `cpu_hotplug_io` check, so it never flagged the block.
**Relocating the base does not work.** The obvious fix - move `ICH9_CPU_HOTPLUG_IO_BASE` off `0x0CD8`
so the pattern changes - clears the scanner but hangs every SMM/OVMF guest: macOS spins ~10 cores at
boot and never reaches login, Windows the same. `OvmfPkg/Include/IndustryStandard/Q35MchIch9.h`
hardcodes `ICH9_CPU_HOTPLUG_BASE 0x0CD8` and OVMF's SMM code reads the register block at that fixed
port, independent of the AML. The non-SMM path (plain OVMF, a bare Linux boot) survives a move; the
SMM guests we actually run do not. A/B confirmed it: macOS reaches login on `0x0CD8`, spins on
`0x0CE4`. Patching OVMF is already on the rejected list.
**The fix keeps the port and hides only the AML operand.** `11` emits the `OperationRegion` base for
a SystemIO region as `Add(0x0C00, 0xD8)` instead of the literal `0x0CD8`. It evaluates to the
identical `0x0CD8` at namespace load - the port QEMU decodes, the port in `_CRS`, and the port OVMF
reads are all unchanged - but the AML is `0x72 0x0B 0x00 0x0C 0x0A 0xD8 ..`, so the `0x0B 0xD8 0x0C`
run never appears. macOS's interpreter evaluates the computed base without complaint.
Verified on QEMU 11.1.2 with this fix in the default series: the DSDT keeps `\_SB.CPUS`/`PRES`/
`Processor(C000..)` and `0x0CD8`, with no `0x0B 0xD8 0x0C` anywhere (iasl-clean); macOS boots to
login and powers off via S5 in ~12 s; VMAware v2.8.1 and HEAD both report `detected=0` / `FIRMWARE=0`
on the stealth guest; Windows (secboot + SMM) and Linux boot unchanged. The opt-in split is gone.
### CLEARED - the SATA controller OVMF must find at 1f.2 (`03-pci-ids`, 1.4.1)
`03-pci-ids` originally relocated the ICH9 SATA controller off its standard slot
(`ICH9_SATA1_DEV` 31 -> 20) to move the dummy-SATA DSDT node away from `00:1f.2`. That breaks
OVMF first-boot for any SATA-booted guest: OVMF expects the q35 AHCI at `00:1f.2`, so with it at
`00:14.2` OVMF never builds the disk boot option and falls through to `Start PXE over IPv4`. Only
macOS exposed this - it boots OpenCore off AHCI; the Windows identity guests boot from NVMe and
never touched the moved controller. A/B confirmed it: stock QEMU reaches the OpenCore picker, the
1.4.0 patched binary PXEs, and restoring `ICH9_SATA1_DEV = 31` reaches the picker again
(`BdsDxe: loading ... from PciRoot(0x0)/Pci(0x1F,0x2)/Sata`).
The relocation was never load-bearing for the score. The dummy-SATA FIRMWARE link
(vmaware.hpp ~9963) fires only on **both** the NameSeg `D0FA` **and** `Name(_ADR, 0x001F0002)`.
With SATA back at `1f.2`, neither is present: QEMU describes device 31's functions under the ICH9
LPC/ISA scope, not through `build_append_pci_bus_devices`, so the controller gets no generic
`_ADR` node at all, and `get_mimic_pci_name` never emits `D0FA` for any devfn. Dumped the DSDT of a
`1f.2`-SATA guest on the fixed build: zero `D0FA`, zero `08 5F 41 44 52 0C 02 00 1F 00`. No vendor
or device ID changes, so `DEVICES` / `PCI_VENDOR` / `CPU_HEURISTIC` are untouched.
Fix: drop the `ICH9_SATA1_DEV` hunk, leave SATA at stock `1f.2`. macOS boots on the default series;
0/85 preserved.
---
## CLEARED - CTYPE and SMBIOS_VM_BIT (the SMBIOS QEMU synthesises)
On Linux, VMAware reads SMBIOS four ways: `DMIDECODE` and `DMI_SCAN` match VM vendor strings in
`dmidecode -t system` and seven `/sys/class/dmi/id` files, `SMBIOS_VM_BIT` tests bit 4 of type 0
byte 19, and `CTYPE` fires on chassis type 1, Other (HEAD e1a9786, vmaware.hpp:8272, :8948, :9023,
:8234). On Windows, `FIRMWARE` also scans the raw tables (`RSMB`, :10369) for `pc-q35` and `Q35 +`
(:9726). The strings are domain identity. The bit, the chassis type and the default strings are
what QEMU emits by itself.
QEMU synthesises every required structure the domain does not supply. Switching that off
(`smbios_defaults = false`) leaves only what the domain passes: either a complete real dump through
`-smbios file=`, or a table with no processor, memory or end-of-table structures. `10-smbios` keeps
it on and fixes what the synthesised structures leak:
| Leak | Fix |
| --- | --- |
| type 3 chassis type hard-coded to 1 (Other) | defaults to 3 (Desktop), set with `-smbios type=3,chassis-type=N` |
| type 0 VM bit set whenever type 0 fields are given | `vm` defaults off; libvirt `<sysinfo>` cannot pass `vm=off` |
| unset product and version strings `Standard PC (Q35 + ICH9, 2009)`, `pc-q35-<ver>` | `Unknown`, like the manufacturer from `04-fw-cfg` |
Measured on a Linux q35 guest, the same score on VMAware v2.8.1 and HEAD:
| SMBIOS from the domain | QEMU | Score |
| --- | --- | --- |
| complete real dump, 52 `file=` structures | defaults off | 0/85 |
| the same dump | defaults on | 0/85, `dmidecode` byte-identical |
| field arguments for types 0-4 with `vm=off` | defaults on, builders unpatched | 1/85, `CTYPE` |
| field arguments for types 0-4, no `vm=off`, `chassis-type` set | `10-smbios` | 0/85 |
Identity is authored as field arguments. A dump through `-smbios file=` adds what field arguments
cannot express (memory module detail, OEM structures, battery) and mixes with them per type: a type
given as `file=` cannot also take field arguments. Windows and macOS guests are not yet measured
against this patch.
--- ---
## OPEN - GPU_CAPABILITIES (weight 20) ## OPEN - GPU_CAPABILITIES (weight 20)
+8 -9
View File
@@ -75,12 +75,6 @@ Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
Put your public key in `C:\ProgramData\ssh\administrators_authorized_keys` for an Administrator Put your public key in `C:\ProgramData\ssh\administrators_authorized_keys` for an Administrator
account. `vm-native-verify` uses that key. account. `vm-native-verify` uses that key.
SSH into the guest fails with `Corrupted MAC on input` until the host has the e1000e offload rule
from the `vfio-native` package. The emulated NIC's TX offloads corrupt integrity-checked traffic on
the host side of the tap; SMB tolerates it, SSH does not. The package installs a udev rule that
turns the offloads off on every libvirt tap as it appears, and `vm-native-setup` says so if it is
missing.
## 3. Make the NVMe driver boot-critical ## 3. Make the NVMe driver boot-critical
The disk is about to move from virtio to emulated NVMe, and Windows only loads boot-start drivers The disk is about to move from virtio to emulated NVMe, and Windows only loads boot-start drivers
@@ -127,9 +121,14 @@ Two things it asks or warns about:
Before the first boot, if the host has less free memory than the guest's RAM, free and compact Before the first boot, if the host has less free memory than the guest's RAM, free and compact
it so the guest lands on transparent hugepages; `vm-native-setup` prints the two commands when it it so the guest lands on transparent hugepages; `vm-native-setup` prints the two commands when it
applies. The NIC stays `e1000e`, so the network survives the driver removal in the next step. Do not use applies. The NIC stays `igb`, so the network survives the driver removal in the next step. Do not use
virtiofs for host files: it is a virtio device the scanner names, and its shared memory backing virtiofs for host files: it is a virtio device the scanner names, and its shared memory backing
blocks transparent hugepages for the whole guest. Share over SMB on the e1000e link instead. blocks transparent hugepages for the whole guest. Share over SMB on the `igb` link instead.
The interface asks for MTU 9000, and the package's hook turns GSO and GRO on for the tap. Together
they take the inbound link from 2.9 to 14 Gbit/s; neither does anything alone. The libvirt network
needs `<mtu size='9000'/>` too, and the guest needs *Jumbo Packet* 9014 with its interface MTU at
9000 - setting the adapter property alone leaves the IP MTU at 1500 and gains nothing.
## 5. Remove the virtio drivers and the agents ## 5. Remove the virtio drivers and the agents
@@ -165,7 +164,7 @@ echo Y | sudo tee /sys/module/kvm_amd/parameters/cpuid_passthrough
vm-native-verify vm-native-verify
``` ```
`vm-native-setup` printed those two lines with your SKU. The switch is host-wide: it applies to `vm-native-setup` printed those two lines with your SKU, or `sudo vm-native-cpuid enable <domain>` does the same on/off automatically around the guest (off for the cold boot, on once its network is up). The switch is host-wide: it applies to
every pinned guest at once, and a cold boot of any of them while it is on hits the enumeration every pinned guest at once, and a cold boot of any of them while it is on hits the enumeration
race, so with several guests turn it off before any boots and on when they are all up. Then the race, so with several guests turn it off before any boots and on when they are all up. Then the
scanner, from the console session, as `docs/TESTING.md` describes. The reference guest reads 1/85. scanner, from the console session, as `docs/TESTING.md` describes. The reference guest reads 1/85.
+1 -1
View File
@@ -12,7 +12,7 @@ pkgbase = vfio-native-kvm-dkms
provides = vfio-native-kvm provides = vfio-native-kvm
conflicts = vfio-native-kvm conflicts = vfio-native-kvm
noextract = linux-7.2.3.tar.xz noextract = linux-7.2.3.tar.xz
source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.1.0 source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.4.0
source = https://cdn.kernel.org/pub/linux/kernel/v7.x/linux-7.2.3.tar.xz source = https://cdn.kernel.org/pub/linux/kernel/v7.x/linux-7.2.3.tar.xz
source = dkms.conf source = dkms.conf
source = dkms-out-of-tree.patch source = dkms-out-of-tree.patch
+1 -1
View File
@@ -27,7 +27,7 @@
pkgname=vfio-native-kvm-dkms pkgname=vfio-native-kvm-dkms
_pkgbase=vfio-native-kvm _pkgbase=vfio-native-kvm
_kver=7.2.3 _kver=7.2.3
_tag=1.1.0 _tag=1.4.1
pkgver=${_kver} pkgver=${_kver}
pkgrel=1 pkgrel=1
pkgdesc="KVM modules patched to raise the exceptions the architecture specifies (DKMS, 7.2.x kernels)" pkgdesc="KVM modules patched to raise the exceptions the architecture specifies (DKMS, 7.2.x kernels)"
+6 -6
View File
@@ -1,7 +1,7 @@
pkgbase = vfio-native-qemu pkgbase = vfio-native-qemu
pkgdesc = QEMU 11.1.1 with the vfio-native platform-identity patches, in /opt/qemu-native pkgdesc = QEMU 11.1.2 with the vfio-native platform-identity patches, in /opt/qemu-native
pkgver = 11.1.1 pkgver = 11.1.2
pkgrel = 1 pkgrel = 2
url = https://git.archworks.co/sandwich/vfio-native url = https://git.archworks.co/sandwich/vfio-native
arch = x86_64 arch = x86_64
license = GPL-2.0-only license = GPL-2.0-only
@@ -38,9 +38,9 @@ pkgbase = vfio-native-qemu
depends = pipewire depends = pipewire
optdepends = vfio-native: the setup tooling that points a domain at this build optdepends = vfio-native: the setup tooling that points a domain at this build
options = !strip options = !strip
source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.1.0 source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.4.0
source = https://download.qemu.org/qemu-11.1.1.tar.xz source = https://download.qemu.org/qemu-11.1.2.tar.xz
sha256sums = SKIP sha256sums = SKIP
sha256sums = 079ffbff8a7111bbc89022107cbabf3bbfd614d5fc9d7cc675991196aca12482 sha256sums = 731b5681e4bb18be313231579b8efd0296c5b015fa36dc533874b639ba838016
pkgname = vfio-native-qemu pkgname = vfio-native-qemu
+6 -6
View File
@@ -1,15 +1,15 @@
# Maintainer: Sandwich <sandwich@archworks.co> # Maintainer: Sandwich <sandwich@archworks.co>
# #
# QEMU 11.1.1 with the platform-identity patches from vfio-native, installed # QEMU 11.1.2 with the platform-identity patches from vfio-native, installed
# under /opt/qemu-native so the system qemu package is never touched. Clears # under /opt/qemu-native so the system qemu package is never touched. Clears
# FIRMWARE, VIRTUAL_DISK, DISK_SERIAL, PCI_VENDOR, DEVICES, QEMU_FW_CFG and # FIRMWARE, VIRTUAL_DISK, DISK_SERIAL, PCI_VENDOR, DEVICES, QEMU_FW_CFG and
# QEMU_USB. Only the x86_64 system emulator is built. # QEMU_USB. Only the x86_64 system emulator is built.
pkgname=vfio-native-qemu pkgname=vfio-native-qemu
_qver=11.1.1 _qver=11.1.2
_tag=1.1.0 _tag=1.4.1
pkgver=${_qver} pkgver=${_qver}
pkgrel=1 pkgrel=3
pkgdesc="QEMU ${_qver} with the vfio-native platform-identity patches, in /opt/qemu-native" pkgdesc="QEMU ${_qver} with the vfio-native platform-identity patches, in /opt/qemu-native"
arch=('x86_64') arch=('x86_64')
url="https://git.archworks.co/sandwich/vfio-native" url="https://git.archworks.co/sandwich/vfio-native"
@@ -24,11 +24,11 @@ options=('!strip')
source=("git+${url}.git#tag=v${_tag}" source=("git+${url}.git#tag=v${_tag}"
"https://download.qemu.org/qemu-${_qver}.tar.xz") "https://download.qemu.org/qemu-${_qver}.tar.xz")
sha256sums=('SKIP' sha256sums=('SKIP'
'079ffbff8a7111bbc89022107cbabf3bbfd614d5fc9d7cc675991196aca12482') '731b5681e4bb18be313231579b8efd0296c5b015fa36dc533874b639ba838016')
prepare() { prepare() {
cd "qemu-${_qver}" cd "qemu-${_qver}"
for p in "${srcdir}"/vfio-native/patches/qemu/0[1-8]-*.patch; do for p in "${srcdir}"/vfio-native/patches/qemu/[0-9][0-9]-*.patch; do
msg2 "applying ${p##*/}" msg2 "applying ${p##*/}"
patch -Np1 --no-backup-if-mismatch -i "$p" patch -Np1 --no-backup-if-mismatch -i "$p"
done done
+2 -2
View File
@@ -1,6 +1,6 @@
pkgbase = vfio-native pkgbase = vfio-native
pkgdesc = Present a libvirt guest as a self-consistent physical machine, and tune it pkgdesc = Present a libvirt guest as a self-consistent physical machine, and tune it
pkgver = 1.1.0 pkgver = 1.4.0
pkgrel = 1 pkgrel = 1
url = https://git.archworks.co/sandwich/vfio-native url = https://git.archworks.co/sandwich/vfio-native
install = vfio-native.install install = vfio-native.install
@@ -17,7 +17,7 @@ pkgbase = vfio-native
optdepends = cpupower: set the host CPU governor optdepends = cpupower: set the host CPU governor
optdepends = vfio-native-kvm-dkms: patched KVM modules for the full level optdepends = vfio-native-kvm-dkms: patched KVM modules for the full level
optdepends = vfio-native-qemu: patched QEMU for the full level optdepends = vfio-native-qemu: patched QEMU for the full level
source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.1.0 source = git+https://git.archworks.co/sandwich/vfio-native.git#tag=v1.4.0
sha256sums = SKIP sha256sums = SKIP
pkgname = vfio-native pkgname = vfio-native
+8 -5
View File
@@ -5,10 +5,10 @@
# Three packages: # Three packages:
# vfio-native scripts, patches, ACPI tables and the benchmark # vfio-native scripts, patches, ACPI tables and the benchmark
# vfio-native-kvm-dkms the patched KVM modules, rebuilt by DKMS per kernel # 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 pkgname=vfio-native
pkgver=1.1.0 pkgver=1.4.1
pkgrel=1 pkgrel=1
pkgdesc="Present a libvirt guest as a self-consistent physical machine, and tune it" pkgdesc="Present a libvirt guest as a self-consistent physical machine, and tune it"
arch=('any') arch=('any')
@@ -29,6 +29,7 @@ package() {
install -Dm755 scripts/setup-vm.sh "${pkgdir}/usr/bin/vm-native-setup" install -Dm755 scripts/setup-vm.sh "${pkgdir}/usr/bin/vm-native-setup"
install -Dm755 scripts/verify-perf.sh "${pkgdir}/usr/bin/vm-native-verify" install -Dm755 scripts/verify-perf.sh "${pkgdir}/usr/bin/vm-native-verify"
install -Dm755 scripts/gpu-passthrough.sh "${pkgdir}/usr/bin/vm-native-gpu" install -Dm755 scripts/gpu-passthrough.sh "${pkgdir}/usr/bin/vm-native-gpu"
install -Dm755 scripts/cpuid-passthrough.sh "${pkgdir}/usr/bin/vm-native-cpuid"
local share="${pkgdir}/usr/share/vfio-native" local share="${pkgdir}/usr/share/vfio-native"
install -Dm644 bench/vmbench.c "${share}/bench/vmbench.c" install -Dm644 bench/vmbench.c "${share}/bench/vmbench.c"
@@ -36,6 +37,9 @@ package() {
install -Dm755 scripts/install-modules.sh "${share}/scripts/install-modules.sh" 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/restore-stock-kvm.sh "${share}/scripts/restore-stock-kvm.sh"
install -Dm755 scripts/generate-tables.py "${share}/scripts/generate-tables.py" 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 install -Dm644 -t "${share}/acpi" acpi/*.aml acpi/*.dsl
install -dm755 "${share}/patches" install -dm755 "${share}/patches"
cp -r patches/. "${share}/patches/" cp -r patches/. "${share}/patches/"
@@ -45,7 +49,6 @@ package() {
# this coexists with whatever hook the host already has. # this coexists with whatever hook the host already has.
install -Dm755 scripts/libvirt-hook-cpu-isolation.sh \ install -Dm755 scripts/libvirt-hook-cpu-isolation.sh \
"${pkgdir}/etc/libvirt/hooks/qemu.d/10-cpu-isolation.sh" "${pkgdir}/etc/libvirt/hooks/qemu.d/10-cpu-isolation.sh"
# e1000e offloads corrupt integrity-checked traffic on libvirt taps; host-wide by nature install -Dm755 scripts/libvirt-hook-vnet-offload.sh \
install -Dm644 scripts/99-vfio-native-vnet-offload.rules \ "${pkgdir}/etc/libvirt/hooks/qemu.d/20-vnet-offload.sh"
"${pkgdir}/usr/lib/udev/rules.d/99-vfio-native-vnet-offload.rules"
} }
+5 -5
View File
@@ -49,11 +49,11 @@ post_install() {
'native' is a good default if you would rather not maintain anything. 'native' is a good default if you would rather not maintain anything.
A udev rule (99-vfio-native-vnet-offload.rules) turns TX offloads off on every A hook at /etc/libvirt/hooks/qemu.d/20-vnet-offload.sh turns GSO and GRO on
libvirt tap as it appears. The emulated e1000e NIC corrupts integrity-checked for the guest's tap. With the interface's MTU 9000 that takes the inbound link
traffic with them on; SSH to the guest fails with "Corrupted MAC on input". from 2.9 to 14 Gbit/s; neither does anything alone. The libvirt network needs
It applies to every VM on the host; the throughput cost on a host<->guest <mtu size='9000'/> too, and the guest needs Jumbo Packet 9014 with its
link is not measurable. interface MTU at 9000.
A libvirt hook is installed at /etc/libvirt/hooks/qemu.d/10-cpu-isolation.sh. A libvirt hook is installed at /etc/libvirt/hooks/qemu.d/10-cpu-isolation.sh.
It keeps host processes off the cores the guest is pinned to, automatically, It keeps host processes off the cores the guest is pinned to, automatically,
+23 -3
View File
@@ -34,7 +34,7 @@ enabled through the `cpuid_passthrough` module parameter after the guest has boo
## QEMU - `qemu/` ## QEMU - `qemu/`
Against QEMU v11.1.1. Apply in numeric order. Against QEMU v11.1.2. Apply in numeric order.
| Patch | Clears | | Patch | Clears |
| --- | --- | | --- | --- |
@@ -46,11 +46,29 @@ Against QEMU v11.1.1. Apply in numeric order.
| `06-audio` | `DEVICES` - HDA codec and controller identity | | `06-audio` | `DEVICES` - HDA codec and controller identity |
| `07-edid` | none. Carried for other detectors; VMAware does not read EDID. | | `07-edid` | none. Carried for other detectors; VMAware does not read EDID. |
| `08-cpu-misc` | none. CPU feature plumbing and one upstream backport. | | `08-cpu-misc` | none. CPU feature plumbing and one upstream backport. |
| `09-firmware-chain` | `FIRMWARE` - the DSDT links current VMAware added after `01-firmware` |
| `10-smbios` | `CTYPE`, `SMBIOS_VM_BIT`, `FIRMWARE` - what QEMU's synthesised SMBIOS leaks: chassis type Other, the type 0 VM bit, `pc-q35` default strings; adds `-smbios type=3,chassis-type=N` |
| `11-macos-cpu-hotplug` | `FIRMWARE` - re-adds the `\_SB.CPUS` CPU objects macOS needs, with the CPU-hotplug I/O base `0x0CD8` hidden from the scanner (renamed `_UID`, `Add()`-split OperationRegion base) rather than removed |
`patch -p1 < qemu/01-firmware.patch` and so on, then build with the prefix set to `/opt` so the The numbered patches are the default series and score 0/85 on both VMAware HEAD and v2.8.1.
system QEMU package is left alone: `03-pci-ids` scopes the virtio subsystem-id change so OVMF still binds virtio (its generic default
goes to Intel/0, virtio devices keep the Red Hat subsystem ids their drivers and OVMF expect). It
also leaves the ICH9 SATA controller at its stock slot `1f.2`: OVMF locates the q35 AHCI there, so
moving it broke first-boot for SATA-booted UEFI guests (macOS fell through to PXE) while buying
nothing for the score - the dummy-SATA FIRMWARE tell needs both the `D0FA` name and `_ADR 0x1F0002`,
and device 31's functions emit neither. See `docs/ANALYSIS.md`.
`11-macos-cpu-hotplug` makes the series universal: macOS boots (it needs the `\_SB.CPUS.Cxxx`
objects from `build_cpus_aml`) and both VMAware builds still read bare metal, with no opt-in. The
block's only tell, the `0x0CD8` CPU-hotplug port that v2.8.1 FIRMWARE byte-matches, is removed at
the AML level without moving the port, so the same series boots Windows and Linux unchanged. The
earlier opt-in split and the relocate-the-base approach are in "Approaches tried and rejected".
Apply in numeric order, then build with the prefix set to `/opt` so the system QEMU package is left
alone:
```sh ```sh
for p in qemu/[0-9][0-9]-*.patch; do patch -p1 < "$p"; done
./configure --prefix=/opt/qemu-native --target-list=x86_64-softmmu ./configure --prefix=/opt/qemu-native --target-list=x86_64-softmmu
ninja -C build && ninja -C build install ninja -C build && ninja -C build install
``` ```
@@ -64,6 +82,8 @@ ninja -C build && ninja -C build install
| CPUID leaf override (Hypervisor-Phantom) | Halved the timing signature, still detected, and *added* a `SINGLE_STEP` detection. | | CPUID leaf override (Hypervisor-Phantom) | Halved the timing signature, still detected, and *added* a `SINGLE_STEP` detection. |
| Clearing `INTERCEPT_CPUID` outright | Guest never boots. | | Clearing `INTERCEPT_CPUID` outright | Guest never boots. |
| Intercept clearing, minimal hypercall | Triple fault, or no effect. | | Intercept clearing, minimal hypercall | Triple fault, or no effect. |
| Opt-in macOS CPU block (`optional/macos-cpu-hotplug`) | Worked but split the series in two; a macOS-capable build scored 2/85 on v2.8.1. Folded into `11` instead. |
| Relocating the CPU-hotplug I/O base off `0x0CD8` | Clears the scanner but hangs every SMM/OVMF guest (macOS spins 10 cores at boot, Windows too): OVMF hardcodes `0x0CD8` in `OvmfPkg` and reads the register block directly. `11` keeps the port and hides only the AML operand. |
`TIMER` has two detectors that OR together. The exception-latency one passes on stock KVM once it `TIMER` has two detectors that OR together. The exception-latency one passes on stock KVM once it
is measured the way VMAware measures it; the instruction-latency one is removable with is measured the way VMAware measures it; the instruction-latency one is removable with
+17 -11
View File
@@ -5,7 +5,7 @@ Targets VMAware check: FIRMWARE
ACPI table fingerprints: PCI link-device names, PNP0A06 resource stubs, the ACPI table fingerprints: PCI link-device names, PNP0A06 resource stubs, the
FADT C-state latencies and the AML the checker string-matches on. FADT C-state latencies and the AML the checker string-matches on.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 01-firmware.patch patch -p1 < 01-firmware.patch
--- ---
@@ -193,7 +193,7 @@ index 8837b69..4001d9d 100644
if (pcms->wdat_enabled == true) { if (pcms->wdat_enabled == true) {
g_assert(pm.tco_io_base); g_assert(pm.tco_io_base);
diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c
index 87162ff..4c303cb 100644 index 87162ff2c..3616c580a 100644
--- a/hw/acpi/pcihp.c --- a/hw/acpi/pcihp.c
+++ b/hw/acpi/pcihp.c +++ b/hw/acpi/pcihp.c
@@ -687,10 +687,10 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, @@ -687,10 +687,10 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */,
@@ -209,7 +209,7 @@ index 87162ff..4c303cb 100644
/* device present, functioning, decoding, not shown in UI */ /* device present, functioning, decoding, not shown in UI */
aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
crs = aml_resource_template(); crs = aml_resource_template();
@@ -699,6 +699,83 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, @@ -699,6 +699,89 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */,
aml_append(scope, dev); aml_append(scope, dev);
} }
@@ -251,7 +251,7 @@ index 87162ff..4c303cb 100644
+ case 5: + case 5:
+ // Slot 5: USB XHCI (QEMU default often) + // Slot 5: USB XHCI (QEMU default often)
+ if (func == 0) sprintf(name, "XHC1"); + if (func == 0) sprintf(name, "XHC1");
+ else sprintf(name, "XHC%X", func); + else sprintf(name, "XHC%X", func + 1);
+ break; + break;
+ +
+ case 6: + case 6:
@@ -261,19 +261,25 @@ index 87162ff..4c303cb 100644
+ break; + break;
+ +
+ case 29: + case 29:
+ // Slot 29 (0x1D): USB EHCI (Intel Standard) + // Slot 29 (0x1D): ICH9 USB, UHCI at functions 0-2 and EHCI at 7
+ if (func == 0) sprintf(name, "EHC1"); + if (func == 7) sprintf(name, "EHC1");
+ else sprintf(name, "EHC%X", func); + else sprintf(name, "UHC%X", func + 1);
+ break; + break;
+ +
+ case 20: + case 20:
+ // Slot 20 (0x14): LPC / SMBus / SATA 2 + // Slot 20 (0x14): SMBus / SATA 2; the q35 LPC bridge is 1f.0
+ if (func == 0) sprintf(name, "LPCB"); // ISA Bridge + if (func == 2) sprintf(name, "SATA"); // 2nd SATA; slot 6 owns SAT0-SAT7
+ else if (func == 2) sprintf(name, "SAT1"); // 2nd SATA
+ else if (func == 3) sprintf(name, "SBUS"); // SMBus + else if (func == 3) sprintf(name, "SBUS"); // SMBus
+ else sprintf(name, "S20%X", func); // Fallback + else sprintf(name, "S20%X", func); // Fallback
+ break; + break;
+ +
+ case 31:
+ // Slot 31 (0x1F): the ICH9 LPC bridge, named as 03-pci-ids' PIRQ field expects
+ if (func == 0) {
+ sprintf(name, "LPCB");
+ break;
+ }
+ /* fall through */
+ default: + default:
+ /* + /*
+ * For generic slots, we use "RP" (Root Port) + devfn (Hex). + * For generic slots, we use "RP" (Root Port) + devfn (Hex).
@@ -293,7 +299,7 @@ index 87162ff..4c303cb 100644
bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus) bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus)
{ {
Aml *method; Aml *method;
@@ -912,7 +989,11 @@ void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) @@ -912,7 +995,11 @@ void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus)
} }
/* start to compose PCI device descriptor */ /* start to compose PCI device descriptor */
+5 -5
View File
@@ -5,7 +5,7 @@ Targets VMAware check: VIRTUAL_DISK / DISK_SERIAL
Storage that announces itself as emulated: 'QEMU HARDDISK', 'QEMU DVD-ROM', Storage that announces itself as emulated: 'QEMU HARDDISK', 'QEMU DVD-ROM',
empty serials and the QEMU vendor string in SCSI inquiry data. empty serials and the QEMU vendor string in SCSI inquiry data.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 02-disk-identity.patch patch -p1 < 02-disk-identity.patch
--- ---
@@ -13,7 +13,7 @@ diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 1b0cce1..0e8ded4 100644 index 1b0cce1..0e8ded4 100644
--- a/hw/scsi/scsi-disk.c --- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c +++ b/hw/scsi/scsi-disk.c
@@ -2551,7 +2551,7 @@ static void scsi_realize(SCSIDevice *dev, Error **errp) @@ -2556,7 +2556,7 @@ static void scsi_realize(SCSIDevice *dev, Error **errp)
s->version = g_strdup(QEMU_HW_VERSION); s->version = g_strdup(QEMU_HW_VERSION);
} }
if (!s->vendor) { if (!s->vendor) {
@@ -22,7 +22,7 @@ index 1b0cce1..0e8ded4 100644
} }
if (s->serial && strlen(s->serial) > MAX_SERIAL_LEN) { if (s->serial && strlen(s->serial) > MAX_SERIAL_LEN) {
error_setg(errp, "The serial number can't be longer than %d characters", error_setg(errp, "The serial number can't be longer than %d characters",
@@ -2615,7 +2615,7 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp) @@ -2620,7 +2620,7 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
s->qdev.blocksize = s->qdev.conf.logical_block_size; s->qdev.blocksize = s->qdev.conf.logical_block_size;
s->qdev.type = TYPE_DISK; s->qdev.type = TYPE_DISK;
if (!s->product) { if (!s->product) {
@@ -31,7 +31,7 @@ index 1b0cce1..0e8ded4 100644
} }
scsi_realize(&s->qdev, errp); scsi_realize(&s->qdev, errp);
} }
@@ -2642,7 +2642,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp) @@ -2647,7 +2647,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
s->qdev.type = TYPE_ROM; s->qdev.type = TYPE_ROM;
s->features |= 1 << SCSI_DISK_F_REMOVABLE; s->features |= 1 << SCSI_DISK_F_REMOVABLE;
if (!s->product) { if (!s->product) {
@@ -59,7 +59,7 @@ diff --git a/hw/ide/core.c b/hw/ide/core.c
index fb9bf11..7f44de2 100644 index fb9bf11..7f44de2 100644
--- a/hw/ide/core.c --- a/hw/ide/core.c
+++ b/hw/ide/core.c +++ b/hw/ide/core.c
@@ -2640,21 +2640,20 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp) @@ -2648,21 +2648,20 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp)
if (dev->serial) { if (dev->serial) {
pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), dev->serial); pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), dev->serial);
} else { } else {
+32 -35
View File
@@ -5,7 +5,10 @@ Targets VMAware check: PCI_VENDOR / DEVICES
PCI vendor, device and subsystem IDs that read as Red Hat / QEMU, and the PCI vendor, device and subsystem IDs that read as Red Hat / QEMU, and the
ICH9 southbridge layout OVMF expects to find at 1f:0. ICH9 southbridge layout OVMF expects to find at 1f:0.
Applies to QEMU v11.1.1. Apply from the source root with: Virtio devices keep Red Hat's subsystem IDs: their vendor ID names them
anyway, and OVMF and the guest virtio drivers only bind with them.
Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 03-pci-ids.patch patch -p1 < 03-pci-ids.patch
--- ---
@@ -24,19 +27,6 @@ index f2448e9..4eea405 100644
#define PCI_DEVICE_ID_QEMU_IPMI 0x1112 #define PCI_DEVICE_ID_QEMU_IPMI 0x1112
/* VMWare (0x15ad) */ /* VMWare (0x15ad) */
@@ -77,9 +77,9 @@ extern bool pci_available;
#define PCI_DEVICE_ID_INTEL_82801IR 0x2922
/* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */
-#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4
-#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4
-#define PCI_SUBDEVICE_ID_QEMU 0x1100
+#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 // VirtIO devices | IVSHMEM Driver | Looking Glass
+#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x8086 // VirtIO devices | IVSHMEM Driver | Looking Glass
+#define PCI_SUBDEVICE_ID_QEMU 0x0000 // VirtIO devices | IVSHMEM Driver | Looking Glass
/* legacy virtio-pci devices */
#define PCI_DEVICE_ID_VIRTIO_NET 0x1000
@@ -100,21 +100,21 @@ extern bool pci_available; @@ -100,21 +100,21 @@ extern bool pci_available;
*/ */
#define PCI_DEVICE_ID_VIRTIO_10_BASE 0x1040 #define PCI_DEVICE_ID_VIRTIO_10_BASE 0x1040
@@ -100,15 +90,6 @@ index 2c35dd0..2b6a39c 100644
#define ICH9_LPC_NB_PIRQS 8 /* PCI A-H */ #define ICH9_LPC_NB_PIRQS 8 /* PCI A-H */
#define ICH9_LPC_PMBASE 0x40 #define ICH9_LPC_PMBASE 0x40
@@ -177,7 +177,7 @@ struct ICH9LPCState {
#define ICH9_GPIO_GSI "gsi"
/* D31:F2 SATA Controller #1 */
-#define ICH9_SATA1_DEV 31
+#define ICH9_SATA1_DEV 20
#define ICH9_SATA1_FUNC 2
/* D31:F0 power management I/O registers
@@ -213,7 +213,7 @@ struct ICH9LPCState { @@ -213,7 +213,7 @@ struct ICH9LPCState {
/* D31:F3 SMBus controller */ /* D31:F3 SMBus controller */
#define TYPE_ICH9_SMB_DEVICE "ICH9-SMB" #define TYPE_ICH9_SMB_DEVICE "ICH9-SMB"
@@ -166,16 +147,32 @@ index e2aab3d..9131b5d 100644
dc->user_creatable = true; dc->user_creatable = true;
vc->get_id = device_vmstate_if_get_id; vc->get_id = device_vmstate_if_get_id;
rc->get_state = device_get_reset_state; rc->get_state = device_get_reset_state;
diff --git a/hw/i386/pc.c b/hw/i386/pc.c diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index f064aa2..cfdbbc1 100644 index d319160..7206e3d 100644
--- a/hw/i386/pc.c --- a/hw/pci/pci.c
+++ b/hw/i386/pc.c +++ b/hw/pci/pci.c
@@ -1689,7 +1689,7 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) @@ -355,8 +355,8 @@ static void pci_irq_handler(void *opaque, int irq_num, int level);
static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom, Error **);
static void pci_del_option_rom(PCIDevice *pdev);
pcmc->pci_enabled = true; -static uint16_t pci_default_sub_vendor_id = PCI_SUBVENDOR_ID_REDHAT_QUMRANET;
pcmc->has_acpi_build = true; -static uint16_t pci_default_sub_device_id = PCI_SUBDEVICE_ID_QEMU;
- pcmc->smbios_defaults = true; +static uint16_t pci_default_sub_vendor_id = PCI_VENDOR_ID_INTEL;
+ pcmc->smbios_defaults = false; +static uint16_t pci_default_sub_device_id = 0;
pcmc->gigabyte_align = true;
pcmc->has_reserved_memory = true; PCIHostStateList pci_host_bridges;
pcmc->enforce_amd_1tb_hole = true;
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 6f5db5f..a7e07b8 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -2029,6 +2029,9 @@ static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
modern = virtio_pci_modern(proxy);
config = proxy->pci_dev.config;
+ /* virtio drivers match Red Hat's subsystem IDs; OVMF needs a device ID >= 0x40 */
+ pci_set_word(config + PCI_SUBSYSTEM_VENDOR_ID, PCI_SUBVENDOR_ID_REDHAT_QUMRANET);
+ pci_set_word(config + PCI_SUBSYSTEM_ID, PCI_SUBDEVICE_ID_QEMU);
if (proxy->class_code) {
pci_config_set_class(config, proxy->class_code);
}
+1 -1
View File
@@ -4,7 +4,7 @@ Targets VMAware check: QEMU_FW_CFG
The fw_cfg device signature, which names QEMU literally. The fw_cfg device signature, which names QEMU literally.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 04-fw-cfg.patch patch -p1 < 04-fw-cfg.patch
--- ---
+1 -1
View File
@@ -4,7 +4,7 @@ Targets VMAware check: QEMU_USB
USB HID descriptors carrying QEMU vendor strings and product IDs. USB HID descriptors carrying QEMU vendor strings and product IDs.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 05-usb-hid.patch patch -p1 < 05-usb-hid.patch
--- ---
+1 -1
View File
@@ -4,7 +4,7 @@ Targets VMAware check: DEVICES
Intel HDA codec and controller identity strings. Intel HDA codec and controller identity strings.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 06-audio.patch patch -p1 < 06-audio.patch
--- ---
+1 -1
View File
@@ -5,7 +5,7 @@ Targets VMAware check: (display identity)
Generated EDID vendor and model. Note VMAware does not read EDID - this is Generated EDID vendor and model. Note VMAware does not read EDID - this is
carried for other detectors, not for the score. carried for other detectors, not for the score.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 07-edid.patch patch -p1 < 07-edid.patch
--- ---
+1 -1
View File
@@ -4,7 +4,7 @@ Targets VMAware check: (CPU model plumbing)
CPU feature-word plumbing and an upstream backport; not aimed at a check. CPU feature-word plumbing and an upstream backport; not aimed at a check.
Applies to QEMU v11.1.1. Apply from the source root with: Applies to QEMU v11.1.2. Apply from the source root with:
patch -p1 < 08-cpu-misc.patch patch -p1 < 08-cpu-misc.patch
--- ---
+239
View File
@@ -0,0 +1,239 @@
Subject: [PATCH] acpi: clear the remaining VMAware FIRMWARE chain links
Targets VMAware check: FIRMWARE
01-firmware cleared the FIRMWARE links known when it was written. Current
VMAware adds more DSDT fingerprints to the same chain, and firmware() returns
on the first match, so the technique keeps firing until every link is gone.
This clears the rest, taking a q35 guest to 0 detections on both the v2.8.1
release and current HEAD. Each change is a rename or a byte-neutral rewrite of
emitted AML; none alters ACPI semantics.
Links cleared:
- PCI interrupt-link query methods IQST/IQCR and the PRR0/PRRI resource
template/field -> PSTA/PCRS/PRS0/PRSI
- TPM Physical Presence Interface regions/method TPP2/TPP3/TPFN -> PPI2/PPI3/PPIF
- zeroed _S5 SLP_TYP: emit the 2-element package form
- ACPI0006 _HID in \_GPE: declared by its full path \_GPE._HID; the
object stays, because macOS will not power off without it
- Q35 MMCONFIG reservation device MCFG -> PCMM
- synthetic GSI link devices: give the no-op _DIS/_SRS a body so the
empty-stub descriptor no longer matches
- PCIe _OSC capability mask: And() operands swapped (commutative)
- PIRQ link _PRS: list the identical IRQ set as {11, 10, 5} so the fixed
5/10/11 descriptor differs
Applies to QEMU v11.1.2, on top of 01-firmware. From the source root:
patch -p1 < 09-firmware-chain.patch
diff --git a/hw/acpi/pci.c b/hw/acpi/pci.c
index 8c7ed1047..0d6a95d67 100644
--- a/hw/acpi/pci.c
+++ b/hw/acpi/pci.c
@@ -326,8 +326,8 @@ Aml *build_pci_host_bridge_osc_method(bool enable_native_pcie_hotplug)
* Allow SHPC (PCI bridges can have SHPC controller)
* Disable PCIe Native Hot-plug if ACPI PCI Hot-plug is enabled.
*/
- aml_append(if_ctx, aml_and(a_ctrl,
- aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl));
+ aml_append(if_ctx, aml_and(
+ aml_int(0x1E | (enable_native_pcie_hotplug ? 0x1 : 0x0)), a_ctrl, a_ctrl));
if_ctx2 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(1))));
/* Unknown revision */
diff --git a/hw/acpi/tpm.c b/hw/acpi/tpm.c
index e70377598..d7c234cc5 100644
--- a/hw/acpi/tpm.c
+++ b/hw/acpi/tpm.c
@@ -39,10 +39,10 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
* the PPI code and parameter (PPRQ, PPRM) to the firmware.
*/
aml_append(dev,
- aml_operation_region("TPP2", AML_SYSTEM_MEMORY,
+ aml_operation_region("PPI2", AML_SYSTEM_MEMORY,
aml_int(ppi_base + 0x100),
0x5A));
- field = aml_field("TPP2", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
+ field = aml_field("PPI2", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
aml_append(field, aml_named_field("PPIN", 8));
aml_append(field, aml_named_field("PPIP", 32));
aml_append(field, aml_named_field("PPRP", 32));
@@ -55,11 +55,11 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
aml_append(dev,
aml_operation_region(
- "TPP3", AML_SYSTEM_MEMORY,
+ "PPI3", AML_SYSTEM_MEMORY,
aml_int(ppi_base +
0x15a /* movv, docs/specs/tpm.rst */),
0x1));
- field = aml_field("TPP3", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+ field = aml_field("PPI3", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
aml_append(field, aml_named_field("MOVV", 8));
aml_append(dev, field);
@@ -67,7 +67,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
* DerefOf in Windows is broken with SYSTEM_MEMORY. Use a dynamic
* operation region inside of a method for getting FUNC[op].
*/
- method = aml_method("TPFN", 1, AML_SERIALIZED);
+ method = aml_method("PPIF", 1, AML_SERIALIZED);
{
Aml *op = aml_arg(0);
ifctx = aml_if(aml_lgreater_equal(op, aml_int(0x100)));
@@ -164,7 +164,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
/* get opcode flags */
aml_append(ifctx2,
- aml_store(aml_call1("TPFN", op), op_flags));
+ aml_store(aml_call1("PPIF", op), op_flags));
/* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */
ifctx3 = aml_if(
@@ -325,7 +325,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
op));
/* get opcode flags */
- aml_append(ifctx2, aml_store(aml_call1("TPFN", op),
+ aml_append(ifctx2, aml_store(aml_call1("PPIF", op),
op_flags));
/* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */
ifctx3 = aml_if(
@@ -397,7 +397,7 @@ void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev, hwaddr ppi_base)
op));
/* get opcode flags */
- aml_append(ifctx2, aml_store(aml_call1("TPFN", op),
+ aml_append(ifctx2, aml_store(aml_call1("PPIF", op),
op_flags));
/* return confirmation status code */
aml_append(ifctx2,
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 4001d9d69..087d5f778 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -515,7 +515,7 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg)
Aml *dev;
Aml *crs;
Aml *method;
- uint32_t irqs[] = {5, 10, 11};
+ uint32_t irqs[] = {11, 10, 5};
dev = aml_device("%s", name);
aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C0F")));
@@ -527,7 +527,7 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg)
aml_append(dev, aml_name_decl("_PRS", crs));
method = aml_method("_STA", 0, AML_NOTSERIALIZED);
- aml_append(method, aml_return(aml_call1("IQST", reg)));
+ aml_append(method, aml_return(aml_call1("PSTA", reg)));
aml_append(dev, method);
method = aml_method("_DIS", 0, AML_NOTSERIALIZED);
@@ -535,12 +535,12 @@ static Aml *build_link_dev(const char *name, uint8_t uid, Aml *reg)
aml_append(dev, method);
method = aml_method("_CRS", 0, AML_NOTSERIALIZED);
- aml_append(method, aml_return(aml_call1("IQCR", reg)));
+ aml_append(method, aml_return(aml_call1("PCRS", reg)));
aml_append(dev, method);
method = aml_method("_SRS", 1, AML_NOTSERIALIZED);
- aml_append(method, aml_create_dword_field(aml_arg(0), aml_int(5), "PRRI"));
- aml_append(method, aml_store(aml_name("PRRI"), reg));
+ aml_append(method, aml_create_dword_field(aml_arg(0), aml_int(5), "PRSI"));
+ aml_append(method, aml_store(aml_name("PRSI"), reg));
aml_append(dev, method);
return dev;
@@ -569,9 +569,11 @@ static Aml *build_gsi_link_dev(const char *name, uint8_t uid, uint8_t gsi)
* _DIS can be no-op because the interrupt cannot be disabled.
*/
method = aml_method("_DIS", 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_store(aml_int(0), aml_local(0)));
aml_append(dev, method);
method = aml_method("_SRS", 1, AML_NOTSERIALIZED);
+ aml_append(method, aml_store(aml_int(0), aml_local(0)));
aml_append(dev, method);
return dev;
@@ -582,28 +584,28 @@ static Aml *build_iqcr_method(bool is_piix4)
{
Aml *if_ctx;
uint32_t irqs;
- Aml *method = aml_method("IQCR", 1, AML_SERIALIZED);
+ Aml *method = aml_method("PCRS", 1, AML_SERIALIZED);
Aml *crs = aml_resource_template();
irqs = 0;
aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL,
AML_ACTIVE_HIGH, AML_SHARED, &irqs, 1));
- aml_append(method, aml_name_decl("PRR0", crs));
+ aml_append(method, aml_name_decl("PRS0", crs));
aml_append(method,
- aml_create_dword_field(aml_name("PRR0"), aml_int(5), "PRRI"));
+ aml_create_dword_field(aml_name("PRS0"), aml_int(5), "PRSI"));
if (is_piix4) {
if_ctx = aml_if(aml_lless(aml_arg(0), aml_int(0x80)));
- aml_append(if_ctx, aml_store(aml_arg(0), aml_name("PRRI")));
+ aml_append(if_ctx, aml_store(aml_arg(0), aml_name("PRSI")));
aml_append(method, if_ctx);
} else {
aml_append(method,
aml_store(aml_and(aml_arg(0), aml_int(0xF), NULL),
- aml_name("PRRI")));
+ aml_name("PRSI")));
}
- aml_append(method, aml_return(aml_name("PRR0")));
+ aml_append(method, aml_return(aml_name("PRS0")));
return method;
}
@@ -611,7 +613,7 @@ static Aml *build_iqcr_method(bool is_piix4)
static Aml *build_irq_status_method(void)
{
Aml *if_ctx;
- Aml *method = aml_method("IQST", 1, AML_NOTSERIALIZED);
+ Aml *method = aml_method("PSTA", 1, AML_NOTSERIALIZED);
if_ctx = aml_if(aml_and(aml_int(0x80), aml_arg(0), NULL));
aml_append(if_ctx, aml_return(aml_int(0x09)));
@@ -801,7 +803,7 @@ static Aml *build_q35_dram_controller(const AcpiMcfgInfo *mcfg)
Aml *resource_template;
/* DRAM controller */
- dev = aml_device("MCFG");
+ dev = aml_device("PCMM");
aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C01")));
resource_template = aml_resource_template();
@@ -986,7 +988,11 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
scope = aml_scope("_GPE");
{
- aml_append(scope, aml_name_decl("_HID", aml_string("ACPI0006")));
+ /*
+ * macOS needs the GPE block device to reach S5; naming it by full
+ * path keeps it without the byte pattern VMAware matches.
+ */
+ aml_append(scope, aml_name_decl("\\_GPE._HID", aml_string("ACPI0006")));
if (machine->nvdimms_state->is_enabled) {
method = aml_method("_E04", 0, AML_NOTSERIALIZED);
aml_append(method, aml_notify(aml_name("\\_SB.NVDR"),
@@ -1193,11 +1199,9 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
aml_append(scope, aml_name_decl("_S4", pkg));
}
- pkg = aml_package(4);
+ pkg = aml_package(2);
aml_append(pkg, aml_int(0)); /* PM1a_CNT.SLP_TYP */
aml_append(pkg, aml_int(0)); /* PM1b_CNT.SLP_TYP not impl. */
- aml_append(pkg, aml_int(0)); /* reserved */
- aml_append(pkg, aml_int(0)); /* reserved */
aml_append(scope, aml_name_decl("_S5", pkg));
aml_append(dsdt, scope);
+97
View File
@@ -0,0 +1,97 @@
Subject: [PATCH] smbios: CTYPE / SMBIOS_VM_BIT / FIRMWARE
Targets VMAware check: CTYPE / SMBIOS_VM_BIT / FIRMWARE
QEMU keeps synthesising every required SMBIOS structure the domain does not
supply, so identity can be set with -smbios type=N field arguments and real
structures passed with -smbios file= only where they are wanted. What QEMU
fills in by itself must not read as a VM:
- type 3 chassis type was hard-coded to 1 (Other), which CTYPE flags. It
defaults to 3 (Desktop) and is set with type=3,chassis-type=N.
- the type 0 "virtual machine" characteristics bit now defaults off, so a
type 0 from libvirt <sysinfo>, which cannot pass vm=off, stays clean.
- unset product and version strings in types 1-4 default to "Unknown"
instead of the machine description and name ("Standard PC (Q35 + ICH9,
2009)", "pc-q35-<ver>"), which FIRMWARE matches when it scans SMBIOS on
Windows.
Applies to QEMU v11.1.2, on top of 04-fw-cfg. Apply from the source root with:
patch -p1 < 10-smbios.patch
---
diff --git a/hw/i386/fw_cfg.c b/hw/i386/fw_cfg.c
index e5fddbc..bfc193c 100644
--- a/hw/i386/fw_cfg.c
+++ b/hw/i386/fw_cfg.c
@@ -71,13 +71,12 @@ void fw_cfg_build_smbios(PCMachineState *pcms, FWCfgState *fw_cfg,
unsigned i, array_count;
MachineState *ms = MACHINE(pcms);
PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms);
- MachineClass *mc = MACHINE_GET_CLASS(pcms);
X86CPU *cpu = X86_CPU(ms->possible_cpus->cpus[0].cpu);
int nr_e820;
if (pcmc->smbios_defaults) {
/* These values are guest ABI, do not change */
- smbios_set_defaults("Unknown", mc->desc, mc->name);
+ smbios_set_defaults("Unknown", "Unknown", "Unknown");
}
/* tell smbios about cpuid version and features */
diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c
index 7d71418..c557069 100644
--- a/hw/smbios/smbios.c
+++ b/hw/smbios/smbios.c
@@ -62,7 +62,8 @@ static struct {
static struct {
const char *manufacturer, *version, *serial, *asset, *sku;
-} type3;
+ uint64_t chassis_type;
+} type3 = { .chassis_type = 0x03 }; /* Desktop: QEMU's "Other" reads as a VM */
/*
* SVVP requires max_speed and current_speed to be set and not being
@@ -282,6 +283,10 @@ static const QemuOptDesc qemu_smbios_type3_opts[] = {
.name = "sku",
.type = QEMU_OPT_STRING,
.help = "SKU number",
+ },{
+ .name = "chassis-type",
+ .type = QEMU_OPT_NUMBER,
+ .help = "chassis type",
},
{ /* end of list */ }
};
@@ -656,7 +661,7 @@ static void smbios_build_type_3_table(void)
SMBIOS_BUILD_TABLE_PRE(3, T3_BASE, true); /* required */
SMBIOS_TABLE_SET_STR(3, manufacturer_str, type3.manufacturer);
- t->type = 0x01; /* Other */
+ t->type = type3.chassis_type;
SMBIOS_TABLE_SET_STR(3, version_str, type3.version);
SMBIOS_TABLE_SET_STR(3, serial_number_str, type3.serial);
SMBIOS_TABLE_SET_STR(3, asset_tag_number_str, type3.asset);
@@ -1413,7 +1418,7 @@ void smbios_entry_add(QemuOpts *opts, Error **errp)
save_opt(&smbios_type0.version, opts, "version");
save_opt(&smbios_type0.date, opts, "date");
smbios_type0.uefi = qemu_opt_get_bool(opts, "uefi", false);
- smbios_type0.vm = qemu_opt_get_bool(opts, "vm", true);
+ smbios_type0.vm = qemu_opt_get_bool(opts, "vm", false);
val = qemu_opt_get(opts, "release");
if (val) {
@@ -1465,6 +1470,12 @@ void smbios_entry_add(QemuOpts *opts, Error **errp)
save_opt(&type3.serial, opts, "serial");
save_opt(&type3.asset, opts, "asset");
save_opt(&type3.sku, opts, "sku");
+ type3.chassis_type = qemu_opt_get_number(opts, "chassis-type",
+ type3.chassis_type);
+ if (type3.chassis_type > UINT8_MAX) {
+ error_setg(errp, "SMBIOS chassis type is too large (> %d)",
+ UINT8_MAX);
+ }
return;
case 4:
if (!qemu_opts_validate(opts, qemu_smbios_type4_opts, errp)) {
+91
View File
@@ -0,0 +1,91 @@
Subject: [PATCH] acpi: keep the CPU objects macOS needs without the FIRMWARE tell
Targets VMAware check: FIRMWARE (CPU hotplug I/O signature)
macOS references \_SB.CPUS.Cxxx from its own CpuPlug SSDT, so the guest needs
QEMU's build_cpus_aml output (the \_SB.PCI0.PRES controller and the \_SB.CPUS
Processor objects) or it hangs in ACPI namespace init. 01-firmware dropped that
block because build_cpus_aml emits the CPU-hotplug register region at I/O port
0x0CD8, which VMAware v2.8.1 FIRMWARE byte-matches as the WordData sequence
0x0B 0xD8 0x0C and flips to a QEMU verdict. HEAD has no such check.
This re-adds the block for every guest (no opt-in) and removes that one tell at
the source rather than removing the whole block:
- the controller _UID is renamed from "CPU Hotplug resources" to "Reserved
resources C", clearing the FIRMWARE string match the same way 01-firmware
renamed the other reserved-resource devices.
- the PRST OperationRegion base is emitted as Add(0x0C00, 0xD8) for a SystemIO
base. It evaluates to the identical 0x0CD8 at namespace load, so the port
QEMU decodes and the port OVMF's SMM code reads directly are both unchanged.
Relocating the base instead was tried and breaks every SMM/OVMF guest (macOS
hangs at boot, Windows too), because OVMF hardcodes 0x0CD8. Splitting the
operand keeps 0x0CD8 live but never places the three matched bytes next to
each other in the AML.
The _CRS IO descriptor keeps 0x0CD8 untouched: a small-resource IO descriptor
encodes its base as 0xD8 0x0C preceded by the 0x47/0x01 descriptor bytes, never
by 0x0B, so it is not the sequence the scanner matches.
Verified against QEMU 11.1.2: macOS boots to login and powers off via S5;
VMAware v2.8.1 and HEAD both read bare metal with FIRMWARE clear on the stealth
guest; Windows and Linux boot unchanged.
Applies to QEMU v11.1.2, on top of 01-firmware. From the source root:
patch -p1 < 11-macos-cpu-hotplug.patch
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -1002,6 +1002,16 @@
}
aml_append(dsdt, scope);
+ {
+ CPUHotplugFeatures opts = {
+ .acpi_1_compatible = true,
+ .smi_path = pm->smi_on_cpuhp ? "\\_SB.PCI0.SMI0.SMIC" : NULL,
+ .fw_unplugs_cpu = pm->smi_on_cpu_unplug,
+ };
+ build_cpus_aml(dsdt, machine, opts, pc_madt_cpu_entry,
+ pm->cpu_hp_io_base, "\\_SB.PCI0", "\\_GPE._E02",
+ AML_SYSTEM_IO);
+ }
crs_range_set_init(&crs_range_set);
bus = PC_MACHINE(machine)->pcibus;
--- a/hw/acpi/cpu.c
+++ b/hw/acpi/cpu.c
@@ -363,7 +363,7 @@
aml_append(cpu_ctrl_dev,
aml_name_decl("_HID", aml_eisaid("PNP0A06")));
aml_append(cpu_ctrl_dev,
- aml_name_decl("_UID", aml_string("CPU Hotplug resources")));
+ aml_name_decl("_UID", aml_string("Reserved resources C")));
aml_append(cpu_ctrl_dev, aml_mutex(CPU_LOCK, 0));
assert((rs == AML_SYSTEM_IO) || (rs == AML_SYSTEM_MEMORY));
@@ -379,9 +379,21 @@
aml_append(cpu_ctrl_dev, aml_name_decl("_CRS", crs));
- /* declare CPU hotplug MMIO region with related access fields */
+ /*
+ * Declare the CPU hotplug register block. The region is at the same
+ * fixed base QEMU decodes and firmware (OVMF SMM) reads directly, so
+ * that is unchanged. Only the base operand's AML encoding is split
+ * across an Add() for a SystemIO base: emitting the 16-bit port as a
+ * single WordData leaks the exact three-byte sequence a VM-detection
+ * scanner byte-matches on. The Add() evaluates to the identical base
+ * at namespace load but never places those bytes contiguously.
+ */
+ Aml *region_base = (rs == AML_SYSTEM_IO)
+ ? aml_add(aml_int(base_addr & 0xff00),
+ aml_int(base_addr & 0x00ff), NULL)
+ : aml_int(base_addr);
aml_append(cpu_ctrl_dev,
- aml_operation_region("PRST", rs, aml_int(base_addr),
+ aml_operation_region("PRST", rs, region_base,
ACPI_CPU_HOTPLUG_REG_LEN));
field = aml_field("PRST", AML_BYTE_ACC, AML_NOLOCK,
@@ -1,8 +0,0 @@
# vfio-native: the emulated e1000e NIC's TX checksum and segmentation offloads
# corrupt packets on the host side of a libvirt tap. SMB tolerates it; SSH fails
# with "Corrupted MAC on input" and any integrity-checked protocol breaks.
# Measured on a Zen 4 host with QEMU 11.1.1. Disabling the offloads on every
# libvirt tap as it appears fixes it; on a host<->guest link the throughput
# cost is not measurable. libvirt's <driver><host .../> attributes are ignored
# for e1000e, and a libvirt hook must not call virsh, hence udev.
ACTION=="add", SUBSYSTEM=="net", KERNEL=="vnet*", RUN+="/usr/bin/ethtool -K %k tx off gso off gro off tso off"
+71
View File
@@ -0,0 +1,71 @@
#!/bin/bash
# Keeps the kvm_amd cpuid_passthrough switch correct across a guest's whole life,
# in-guest reboots included.
#
# The switch must be OFF (N) while the guest enumerates CPUID at boot or Windows
# hangs, and ON (Y) once it is up, where it clears the timer detection. The libvirt
# hook only fires at VM start and stop, so a guest-initiated reboot would otherwise
# re-enumerate with the switch still Y and hang. This watcher drops it to N on every
# QEMU RESET and raises it again once the guest's NIC is back up.
#
# Readiness signal: the guest tap's rx_packets counter growing past a baseline. It
# only moves once the guest NIC driver has loaded, well past CPU enumeration, and it
# needs nothing enabled inside the guest. The counter is cumulative and does NOT
# reset on an in-guest reboot, so readiness is growth past the value captured at the
# reset, not an absolute threshold.
#
# Launched as a transient systemd unit by the cpuid-passthrough hook, so it is free
# to call virsh (the hook itself must not - that deadlocks libvirtd).
DOMAIN=$1
BRAND=$2
PARAM=/sys/module/kvm_amd/parameters
V="virsh -c qemu:///system"
running() { [ "$($V domstate "$DOMAIN" 2>/dev/null)" = running ]; }
rx() { cat "$RX" 2>/dev/null || echo 0; }
set_N() { echo N > "$PARAM/cpuid_passthrough"; }
set_Y() { printf '%s' "$BRAND" > "$PARAM/brand_string"; echo Y > "$PARAM/cpuid_passthrough"; }
# Wait until the tap rx counter grows at least 4 past $1 (guest NIC driver up again).
# The iteration floor keeps a stray pre-OS packet (a UEFI netboot attempt) from
# tripping the flip before the guest is even past its interrupt and timer setup.
wait_net_up() {
local base=$1 i=0
for _ in $(seq 1 100); do # ~300 s cap, then raise anyway if still up
i=$((i + 1))
running || return 1
[ "$i" -ge 4 ] && [ -n "$tap" ] && [ -r "$RX" ] &&
[ "$(rx)" -ge "$((base + 4))" ] && return 0
sleep 3
done
return 0
}
# The hook arms this watcher at prepare, before QEMU starts, so the domain is not
# yet running and its tap does not exist. Wait for the domain, then read the tap;
# otherwise the first running check in wait_net_up bails and the flip never happens.
for _ in $(seq 1 120); do running && break; sleep 1; done
running || exit 0
tap=$($V domiflist "$DOMAIN" 2>/dev/null | awk '$1 ~ /^(vnet|tap|macvtap)/ {print $1; exit}')
RX="/sys/class/net/$tap/statistics/rx_packets"
# initial cold boot: wait for the network, then harden
wait_net_up 0
running || exit 0
set_Y
logger -t vfio-cpuid "$DOMAIN network up: cpuid_passthrough=Y"
# every in-guest reboot fires a QEMU RESET: drop to N for the re-enumeration, then
# raise it again once the guest's NIC is back. --loop streams one line per reset.
$V qemu-monitor-event --domain "$DOMAIN" --event RESET --loop 2>/dev/null | while read -r _; do
running || continue
base=$(rx)
set_N
logger -t vfio-cpuid "$DOMAIN reset: cpuid_passthrough=N for re-enumeration"
wait_net_up "$base" || continue
running || continue
set_Y
logger -t vfio-cpuid "$DOMAIN back up: cpuid_passthrough=Y"
done
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# vm-native-cpuid - manage the automatic cpuid_passthrough toggle for hardened guests.
#
# vm-native-cpuid enable <domain> apply the automatic cpuid passthrough to <domain>
# vm-native-cpuid disable <domain> stop applying it to <domain>
# vm-native-cpuid status show the config and the live module state
# vm-native-cpuid revert remove the hook, watcher and config; reset the module
#
# The switch and brand string are one global kvm_amd parameter, so it serves one
# hardened guest at a time. It is off until you enable it here - no domain is
# touched otherwise. Around an enabled guest the libvirt hook forces the switch off
# for the cold boot, a watcher flips it on once the guest's network is up, and it
# goes off again on stop.
set -uo pipefail
CONF=/etc/vfio-native/cpuid-passthrough.conf
HOOK=/etc/libvirt/hooks/qemu.d/30-cpuid-passthrough.sh
WATCH=/usr/lib/vfio-native/cpuid-passthrough-watch
PARAM=/sys/module/kvm_amd/parameters
SELF=$(cd "$(dirname "$0")/.." 2>/dev/null && pwd)
die() { echo "$*" >&2; exit 1; }
need_root() { [ "$(id -u)" = 0 ] || die "run this as root"; }
load() { ENABLED=yes; DOMAINS=""; [ -r "$CONF" ] && . "$CONF"; }
save() { mkdir -p "$(dirname "$CONF")"; printf 'ENABLED=%s\nDOMAINS="%s"\n' "$ENABLED" "$DOMAINS" > "$CONF"; }
# copy the hook and watcher into place from the installed tree or this checkout
install_hook() {
local src=""
for d in /usr/share/vfio-native "$SELF"; do
[ -f "$d/scripts/libvirt-hook-cpuid-passthrough.sh" ] && src="$d"
done
[ -n "$src" ] || die "cannot find the hook source (install vfio-native, or run from a checkout)"
install -Dm755 "$src/scripts/libvirt-hook-cpuid-passthrough.sh" "$HOOK"
install -Dm755 "$src/scripts/cpuid-passthrough-watch" "$WATCH"
}
case "${1:-}" in
enable)
need_root; [ -n "${2:-}" ] || die "usage: vm-native-cpuid enable <domain>"
load; install_hook; ENABLED=yes
case " $DOMAINS " in *" $2 "*) ;; *) DOMAINS="${DOMAINS:+$DOMAINS }$2" ;; esac
save
echo "$2 enabled. cpuid passthrough is now automatic for: $DOMAINS"
echo "on start it forces N for the cold boot, flips Y once the guest is on the network, N again on stop."
;;
disable)
need_root; [ -n "${2:-}" ] || die "usage: vm-native-cpuid disable <domain>"
load; DOMAINS=$(printf ' %s ' "$DOMAINS" | sed "s/ $2 / /g" | xargs || true); save
systemctl stop "vfio-cpuid-watch-$2.service" 2>/dev/null || true
echo "$2 disabled. remaining: ${DOMAINS:-none}"
;;
status)
load
echo "config: $CONF"
echo "enabled: $ENABLED"
echo "domains: ${DOMAINS:-none}"
echo "hook: $([ -f "$HOOK" ] && echo installed || echo 'not installed')"
if [ -r "$PARAM/cpuid_passthrough" ]; then
echo "module: cpuid_passthrough=$(cat "$PARAM/cpuid_passthrough") brand='$(cat "$PARAM/brand_string")'"
else
echo "module: patched kvm_amd not loaded"
fi
;;
revert)
need_root; load
for d in $DOMAINS; do systemctl stop "vfio-cpuid-watch-$d.service" 2>/dev/null || true; done
rm -f "$HOOK" "$WATCH" "$CONF" /run/vfio-native/cpuid-active
[ -w "$PARAM/cpuid_passthrough" ] && echo N > "$PARAM/cpuid_passthrough"
echo "reverted: hook, watcher and config removed; cpuid_passthrough=N."
;;
*)
sed -n '2,13p' "$0" | sed 's/^#\( \|$\)//'
;;
esac
+22 -4
View File
@@ -57,6 +57,24 @@ group_members() {
done done
} }
# The functions to hand to the guest with GPU $1: every device in its IOMMU group
# (mandatory for vfio), plus any sibling function of the same PCI device that is
# itself a display or HDMI/DP audio controller. An APU parks its PSP and USB
# controllers on the same PCI device in separate IOMMU groups - those are the
# host's, so siblings are filtered by class and never pulled in blindly.
passthrough_devs() {
local d="$1" m cls
{
group_members "$d"
for m in "/sys/bus/pci/devices/${d%.*}".*; do
[ -e "$m" ] || continue
m=$(basename "$m")
cls=$(cat "/sys/bus/pci/devices/$m/class" 2>/dev/null)
case "$cls" in 0x03*|0x0403*) echo "$m";; esac
done
} | sort -u
}
driver_of() { driver_of() {
local l local l
l=$(readlink -f "/sys/bus/pci/devices/$1/driver" 2>/dev/null) l=$(readlink -f "/sys/bus/pci/devices/$1/driver" 2>/dev/null)
@@ -79,7 +97,7 @@ drives_display() {
ids_of() { # vendor:device for vfio-pci binding ids_of() { # vendor:device for vfio-pci binding
local d local d
for d in $(group_members "$1"); do for d in $(passthrough_devs "$1"); do
local cls; cls=$(cat "/sys/bus/pci/devices/$d/class" 2>/dev/null) local cls; cls=$(cat "/sys/bus/pci/devices/$d/class" 2>/dev/null)
# only real functions of the card: skip bridges (class 0x0604xx) # only real functions of the card: skip bridges (class 0x0604xx)
case "$cls" in 0x0604*) continue;; esac case "$cls" in 0x0604*) continue;; esac
@@ -219,7 +237,7 @@ prepare)
# 1. stop whatever is holding the DRM device # 1. stop whatever is holding the DRM device
dm=$(systemctl list-units --type=service --state=running --no-legend 2>/dev/null | dm=$(systemctl list-units --type=service --state=running --no-legend 2>/dev/null |
awk '{print $1}' | grep -xE '(gdm|sddm|lightdm|lxdm|greetd|display-manager)\.service' | head -1) awk '{print $1}' | grep -xE '((gdm|sddm|lightdm|lxdm|greetd|ly|emptty|lemurs)(@[a-z0-9-]+)?|display-manager)\.service' | head -1)
if [ -n "$dm" ]; then if [ -n "$dm" ]; then
echo "$dm" > "$STATE/dm" echo "$dm" > "$STATE/dm"
log "stopping $dm" log "stopping $dm"
@@ -303,7 +321,7 @@ apply() {
"$v" -c qemu:///system dumpxml --inactive "$dom" > "$backup/$dom.gpu-current.xml" "$v" -c qemu:///system dumpxml --inactive "$dom" > "$backup/$dom.gpu-current.xml"
grep -q 'ua-vfionative-gpu' "$backup/$dom.gpu-current.xml" || cp "$backup/$dom.gpu-current.xml" "$backup/$dom.before-gpu.xml" grep -q 'ua-vfionative-gpu' "$backup/$dom.gpu-current.xml" || cp "$backup/$dom.gpu-current.xml" "$backup/$dom.before-gpu.xml"
local devs="" m local devs="" m
for m in $( { group_members "$d"; ls -d "/sys/bus/pci/devices/${d%.*}".* | xargs -n1 basename; } | sort -u); do for m in $(passthrough_devs "$d"); do
case "$(cat "/sys/bus/pci/devices/$m/class" 2>/dev/null)" in 0x0604*) continue;; esac case "$(cat "/sys/bus/pci/devices/$m/class" 2>/dev/null)" in 0x0604*) continue;; esac
IFS=':.' read -r dm bs sl fn <<< "$m" IFS=':.' read -r dm bs sl fn <<< "$m"
devs+=" <hostdev mode='subsystem' type='pci' managed='yes'>\n <source>\n" devs+=" <hostdev mode='subsystem' type='pci' managed='yes'>\n <source>\n"
@@ -330,7 +348,7 @@ xml() {
echo "Add this to the domain, inside <devices>. Every device in the GPU's" echo "Add this to the domain, inside <devices>. Every device in the GPU's"
echo "IOMMU group has to go together:" echo "IOMMU group has to go together:"
echo echo
for m in $(group_members "$d"); do for m in $(passthrough_devs "$d"); do
local cls; cls=$(cat "/sys/bus/pci/devices/$m/class" 2>/dev/null) local cls; cls=$(cat "/sys/bus/pci/devices/$m/class" 2>/dev/null)
case "$cls" in 0x0604*) continue;; esac case "$cls" in 0x0604*) continue;; esac
IFS=':. ' read -r dom bus slot fn <<< "$(echo "$m" | tr ':.' ' ')" IFS=':. ' read -r dom bus slot fn <<< "$(echo "$m" | tr ':.' ' ')"
+13 -9
View File
@@ -5,7 +5,8 @@
# #
# On start it reads the domain's own <cputune> from the XML libvirt passes on # On start it reads the domain's own <cputune> from the XML libvirt passes on
# stdin, works out which host CPUs the guest occupies, and confines the systemd # stdin, works out which host CPUs the guest occupies, and confines the systemd
# slices to the rest. On stop it hands everything back. # slices to the rest. On stop it puts back what the slices had before, so a
# partition someone else set survives a guest's lifetime.
# #
# Measured on a 16 vCPU guest pinned to one CCD of a 7950X: stalls over 10 us in # Measured on a 16 vCPU guest pinned to one CCD of a 7950X: stalls over 10 us in
# a 10 second window fell from ~100 to ~52, and CPU throughput rose about 2%. # a 10 second window fell from ~100 to ~52, and CPU throughput rose about 2%.
@@ -19,12 +20,7 @@ OPERATION="$2"
ALL=$(cat /sys/devices/system/cpu/present) # not nproc: that honours this hook's own affinity ALL=$(cat /sys/devices/system/cpu/present) # not nproc: that honours this hook's own affinity
NCPU=$(( ${ALL##*-} + 1 )) NCPU=$(( ${ALL##*-} + 1 ))
SLICES="system.slice user.slice init.scope" SLICES="system.slice user.slice init.scope"
STATE="/run/vfio-native/cpu-isolation/$DOMAIN" # each slice's AllowedCPUs before we confined it
restore() {
for s in $SLICES; do
systemctl set-property --runtime -- "$s" AllowedCPUs="$ALL" 2>/dev/null
done
}
case "$OPERATION" in case "$OPERATION" in
prepare) prepare)
@@ -62,6 +58,10 @@ PY
) )
[ -n "$HOST" ] || exit 0 # nothing left for the host, or it failed [ -n "$HOST" ] || exit 0 # nothing left for the host, or it failed
mkdir -p "${STATE%/*}"
[ -f "$STATE" ] || for s in $SLICES; do
printf '%s=%s\n' "$s" "$(systemctl show -p AllowedCPUs --value -- "$s")"
done > "$STATE"
for s in $SLICES; do for s in $SLICES; do
systemctl set-property --runtime -- "$s" AllowedCPUs="$HOST" 2>/dev/null systemctl set-property --runtime -- "$s" AllowedCPUs="$HOST" 2>/dev/null
done done
@@ -69,8 +69,12 @@ PY
;; ;;
release|stopped) release|stopped)
restore [ -f "$STATE" ] || exit 0 # this hook confined nothing for the domain
logger -t libvirt-cpu-isolation "$DOMAIN $OPERATION: host slices restored to $ALL" while IFS='=' read -r s cpus; do
systemctl set-property --runtime -- "$s" AllowedCPUs="$cpus" 2>/dev/null
done < "$STATE"
logger -t libvirt-cpu-isolation "$DOMAIN $OPERATION: host slices restored to $(tr '\n' ' ' < "$STATE")"
rm -f "$STATE"
;; ;;
*) *)
# Deliberately does nothing. An earlier version called virsh here to check # Deliberately does nothing. An earlier version called virsh here to check
+68
View File
@@ -0,0 +1,68 @@
#!/bin/bash
# libvirt qemu hook: drive the kvm_amd cpuid_passthrough switch around a guest's life.
#
# The switch must be OFF while a guest cold-boots - raw CPUID changes shape mid
# enumeration and Windows hangs - and ON once the guest is up, where it clears the
# TIMER detection. This applies only to the domains you opt in with
# `vm-native-cpuid enable <domain>`; it does nothing to any other guest.
#
# On start it sets the brand string from the guest's declared SKU and forces N,
# then a detached watcher flips Y once the guest's network is up; on stop it sets N.
# cpuid_passthrough and brand_string are one global kvm_amd parameter, so a single
# hardened guest is served at a time; a second is logged and left alone.
#
# Exits 0 on every path. The hook never calls virsh (that deadlocks libvirtd); the
# watcher that does is a transient systemd unit and runs after this returns.
CONF=/etc/vfio-native/cpuid-passthrough.conf
ENABLED=yes
DOMAINS=""
[ -r "$CONF" ] && . "$CONF"
[ "$ENABLED" = yes ] || exit 0
PARAM=/sys/module/kvm_amd/parameters
[ -w "$PARAM/cpuid_passthrough" ] || exit 0 # stock module, or not an AMD host
DOMAIN=$1
OPERATION=$2
case " $DOMAINS " in *" $DOMAIN "*) ;; *) exit 0 ;; esac # not an opted-in domain
RUN=/run/vfio-native
ACTIVE=$RUN/cpuid-active
WATCH=/usr/lib/vfio-native/cpuid-passthrough-watch
case "$OPERATION" in
prepare)
# the guest's declared SKU, straight from the -cpu model-id in the XML on stdin
BRAND=$(grep -oE "model-id=[^'\"]+" | head -1 | sed 's/^model-id=//')
[ -n "$BRAND" ] || exit 0 # not a full-fidelity guest
mkdir -p "$RUN"
held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" )
if [ -n "$held" ] && [ "$held" != "$DOMAIN" ]; then
logger -t vfio-cpuid "cpuid passthrough held by $held; $DOMAIN left unhardened (one guest at a time)"
exit 0
fi
printf '%s' "$BRAND" > "$PARAM/brand_string"
echo N > "$PARAM/cpuid_passthrough" # safe for the cold boot
printf '%s:%s' "$DOMAIN" "$BRAND" > "$ACTIVE"
logger -t vfio-cpuid "$DOMAIN start: brand='$BRAND' cpuid_passthrough=N, watcher armed"
# transient systemd unit, not a bare background job: libvirt reaps anything left
# in the hook's own process tree; systemd-run escapes it
[ -x "$WATCH" ] && systemd-run --collect --quiet \
--unit="vfio-cpuid-watch-${DOMAIN}" "$WATCH" "$DOMAIN" "$BRAND"
;;
release|stopped)
held=$( [ -e "$ACTIVE" ] && cut -d: -f1 "$ACTIVE" )
[ "$held" = "$DOMAIN" ] || exit 0
systemctl stop "vfio-cpuid-watch-${DOMAIN}.service" 2>/dev/null
echo N > "$PARAM/cpuid_passthrough"
rm -f "$ACTIVE"
logger -t vfio-cpuid "$DOMAIN $OPERATION: cpuid_passthrough=N"
;;
esac
exit 0
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# libvirt qemu hook: turn GSO and GRO on for the guest's tap.
#
# QEMU leaves them off and sets the tap up after udev has run, so it has to
# happen here. Paired with MTU 9000 they are the difference between 2.5 and
# 14 Gbit/s into the guest; neither helps alone. Tap names come from the domain
# XML on stdin, so the hook never calls virsh, which would deadlock libvirtd.
[ "$2" = started ] || exit 0
for tap in $(grep -oE "<target dev='(vnet|tap|macvtap)[^']*'" | sed "s/.*dev='//; s/'$//"); do
ethtool -K "$tap" gso on gro on 2>/dev/null
done
exit 0
+192 -22
View File
@@ -11,8 +11,14 @@
# -s on | off Secure Boot with enrolled keys (default on) # -s on | off Secure Boot with enrolled keys (default on)
# -u none | auto | <vid:pid,...|0000:bb:dd.f,...> USB passthrough # -u none | auto | <vid:pid,...|0000:bb:dd.f,...> USB passthrough
# -r randomize the hardware identity (serials, MAC, memory modules) # -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 # -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: # 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 # 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 # 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 # 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. # answers one question in advance, and -y takes every default.
DOM=win11; PROFILE=""; CORES=""; RAMG=""; ASSUME=0; SECBOOT=""; USBSPEC=""; RANDOMIZE="" DOM=win11; PROFILE=""; CORES=""; RAMG=""; ASSUME=0; SECBOOT=""; USBSPEC=""; RANDOMIZE=""; DMIBLOB=""
while getopts "d:p:c:m:s:u:ryh" o; do case $o in while getopts "d:p:c:m:s:u:S:ryh" o; do case $o in
d) DOM=$OPTARG;; p) PROFILE=$OPTARG;; c) CORES=$OPTARG;; 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 # 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;; h) sed -n '2,/^[^#]/p' "$0" | sed "\$d; s/^# \?//; s|@@SELFNAME@@|$(basename "$0")|g"; exit 0;;
esac; done esac; done
@@ -302,6 +308,24 @@ gen_identity() {
echo "MEM_PART='${mem_part:-KF556C40BB-16}'" echo "MEM_PART='${mem_part:-KF556C40BB-16}'"
echo "MEM_SPEED='${mem_speed:-5600}'" echo "MEM_SPEED='${mem_speed:-5600}'"
echo "MEM_SERIAL='$(rnd 8 'A-F0-9')'" 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" } > "$DATA/identity.env"
} }
mkdir -p "$DATA" mkdir -p "$DATA"
@@ -321,6 +345,32 @@ if [ "$RANDOMIZE" = 1 ] || [ ! -f "$DATA/identity.env" ]; then gen_identity; fi
# shellcheck disable=SC1091 # shellcheck disable=SC1091
. "$DATA/identity.env" . "$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 --- # --------------------------------------------------------- USB passthrough ---
# Passing a whole controller is cleaner than passing devices one by one: no # 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 # emulated hub, no hotplug, nothing in the guest that says "redirected". It is
@@ -504,9 +554,14 @@ echo " binary $EMULATOR"
echo " memory ${RAMG} GiB" echo " memory ${RAMG} GiB"
[ -n "$MODEL" ] && [ "$PROFILE" != tuned ] && echo " declares $MODEL" [ -n "$MODEL" ] && [ "$PROFILE" != tuned ] && echo " declares $MODEL"
if [ "$PROFILE" != tuned ]; then 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" [ "$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" [ "$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" [ "$PROFILE" = full ] && echo " display none - the guest console goes dark, use SSH or RDP"
fi fi
[ -n "$USB_HOSTDEVS" ] && echo " usb $(echo "$USB_HOSTDEVS" | grep -c '<hostdev') hostdev(s): $USBSPEC" [ -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" [ "$SMT" = 2 ] && PINS+=" <vcpupin vcpu='$((k*SMT+1))' cpuset='$(sib "${GUEST[$k]}")'/>\n"
done 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" \ PROFILE="$PROFILE" VENDOR="$VENDOR" VCPUS="$VCPUS" CORES="$CORES" SMT="$SMT" RAMG="$RAMG" \
MODEL="$MODEL" EMULATOR="$EMULATOR" PINS="$PINS" \ MODEL="$MODEL" EMULATOR="$EMULATOR" PINS="$PINS" \
EMUSET="$(pair "$EMU")" IOCSET="$(pair "$IOC")" NUMANODE="$([ "$NNODES" -gt 1 ] && echo "$GNODE")" \ 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_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_CHASSIS_VERSION="$(dmi chassis_version)" DMI_BIOS_VENDOR="$(dmi bios_vendor)" DMI_BIOS_VERSION="$(dmi bios_version)" \
DMI_BIOS_DATE="$(dmi bios_date)" \ 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' python3 - "$BACKUP/$DOM.before-setup.xml" "$BACKUP/$DOM.setup.xml" <<'XMLGEN_END'
import io, os, re, sys import io, os, re, sys
from xml.sax.saxutils import escape 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"]) prof, vcpus, cores, smt = E["PROFILE"], int(E["VCPUS"]), int(E["CORES"]), int(E["SMT"])
s = io.open(src, encoding="utf-8").read() s = io.open(src, encoding="utf-8").read()
conformant = prof != "tuned" conformant = prof != "tuned"
target = E.get("TARGET_MODE") or ""
kib = int(E["RAMG"]) * 1024 * 1024 kib = int(E["RAMG"]) * 1024 * 1024
s = re.sub(r"<memory unit='KiB'>\d+</memory>", "<memory unit='KiB'>%d</memory>" % kib, s, count=1) 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" s = s.replace(" <devices>", " <pm>\n <suspend-to-mem enabled='yes'/>\n"
" <suspend-to-disk enabled='yes'/>\n </pm>\n <devices>", 1) " <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) s = re.sub(r"version=AMD Ryzen [^,]*,", "version=%s," % E["MODEL"], s)
if conformant and E["CONVERT"] == "1": if conformant and E["CONVERT"] == "1":
@@ -637,8 +705,11 @@ if conformant and E["CONVERT"] == "1":
if "device='disk'" not in d or ("bus='nvme'" in d and "<serial>" in d): if "device='disk'" not in d or ("bus='nvme'" in d and "<serial>" in d):
return d return d
d = re.sub(r"<target dev='([^']*)' bus='(virtio|sata|scsi)'/>", r"<target dev='\1' bus='nvme'/>", d) d = re.sub(r"<target dev='([^']*)' bus='(virtio|sata|scsi)'/>", r"<target dev='\1' bus='nvme'/>", d)
# cache='none' is O_DIRECT: on btrfs the guest can change a page while the
# write is in flight, so the stored checksum never matches and later reads
# fail with EIO. Buffered writes hand the filesystem a stable page.
d = re.sub(r"<driver name='qemu' type='([^']*)'[^/]*/>", d = re.sub(r"<driver name='qemu' type='([^']*)'[^/]*/>",
r"<driver name='qemu' type='\1' cache='none' io='native' discard='unmap'/>", d) r"<driver name='qemu' type='\1' cache='writeback' io='threads' discard='unmap'/>", d)
d = re.sub(r"\s*<address type='(pci|drive)'[^/]*/>", "", d) d = re.sub(r"\s*<address type='(pci|drive)'[^/]*/>", "", d)
if "<serial>" not in d: if "<serial>" not in d:
serial = E["NVME_SERIAL"] if n[0] == 0 else E["NVME_SERIAL"][:-1] + "0123456789ABCDEF"[n[0] % 16] serial = E["NVME_SERIAL"] if n[0] == 0 else E["NVME_SERIAL"][:-1] + "0123456789ABCDEF"[n[0] % 16]
@@ -658,11 +729,50 @@ if conformant and E["CONVERT"] == "1":
s = re.sub(r"\s*<input type='[^']*' bus='virtio'/>", "", s) s = re.sub(r"\s*<input type='[^']*' bus='virtio'/>", "", s)
s = re.sub(r"<memballoon model='virtio'>.*?</memballoon>", "<memballoon model='none'/>", s, flags=re.S) s = re.sub(r"<memballoon model='virtio'>.*?</memballoon>", "<memballoon model='none'/>", s, flags=re.S)
s = re.sub(r"<memballoon model='virtio'/>", "<memballoon model='none'/>", s) s = re.sub(r"<memballoon model='virtio'/>", "<memballoon model='none'/>", s)
s = re.sub(r"<model type='virtio'/>(\s*<driver [^/]*/>)?", "<model type='e1000e'/>", s) # jumbo frames are the single biggest win on the host<->guest link: the emulated
# NIC is packet-rate bound, so 9000-byte frames cut the per-packet cost the guest
# pays on receive. Measured 2922 -> 14232 Mbit/s inbound on igb, byte-exact clean.
s = re.sub(r"<model type='virtio'/>(\s*<driver [^/]*/>)?",
"<model type='igb'/>\n <mtu size='9000'/>", s)
if prof == "full": if prof == "full":
s = re.sub(r"<video>.*?</video>", "<video>\n <model type='none'/>\n </video>", s, flags=re.S) 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": if E["RANDOMIZE"] == "1":
s = re.sub(r"<mac address='[^']*'/>", "<mac address='%s'/>" % E["MAC"], s, count=1) s = re.sub(r"<mac address='[^']*'/>", "<mac address='%s'/>" % E["MAC"], s, count=1)
def ent(name, key): def ent(name, key):
@@ -682,6 +792,7 @@ if conformant:
if "<smbios mode=" not in s: if "<smbios mode=" not in s:
s = s.replace(" </os>", " <smbios mode='sysinfo'/>\n </os>", 1) s = s.replace(" </os>", " <smbios mode='sysinfo'/>\n </os>", 1)
if conformant:
if E["SECBOOT"] == "on": if E["SECBOOT"] == "on":
s = re.sub(r"<os firmware='efi'>", "<os>", s, count=1) s = re.sub(r"<os firmware='efi'>", "<os>", s, count=1)
s = re.sub(r"\s*<firmware>.*?</firmware>", "", s, flags=re.S) s = re.sub(r"\s*<firmware>.*?</firmware>", "", s, flags=re.S)
@@ -695,6 +806,17 @@ if conformant:
if "<smm state='on'/>" not in s: if "<smm state='on'/>" not in s:
s = s.replace(" </features>", " <smm state='on'/>\n </features>", 1) 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 # 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) 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"]: if E["USB_HOSTDEVS"]:
@@ -719,22 +841,27 @@ fi
# are replaced; anything else already in the block is left alone. # are replaced; anything else already in the block is left alone.
QARGS=() QARGS=()
if [ "$PROFILE" != tuned ]; then if [ "$PROFILE" != tuned ]; then
# 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}"; } kb() { local s; s=$(cat "/sys/devices/system/cpu/cpu0/cache/index$1/size"); echo "${s%K}"; }
SPAN=$(( (CORES + MAXC - 1) / MAXC )) SPAN=$(( (CORES + MAXC - 1) / MAXC ))
python3 "$SELF/scripts/generate-tables.py" --output-dir "$DATA" \ python3 "$SELF/scripts/generate-tables.py" --output-dir "$DATA" \
--cache-l1 $(( CORES * ($(kb 0) + $(kb 1)) )) --cache-l2 $(( CORES * $(kb 2) )) \ --cache-l1 $(( CORES * ($(kb 0) + $(kb 1)) )) --cache-l2 $(( CORES * $(kb 2) )) \
--cache-l3 $(( SPAN * $(kb 3) )) >/dev/null --cache-l3 $(( SPAN * $(kb 3) )) >/dev/null
for f in "$DATA"/type*.bin; do QARGS+=("-smbios" "file=$f"); done for f in "$DATA"/type*.bin; do QARGS+=("-smbios" "file=$f"); done
fi
if [ -d "$SELF/acpi" ]; then if [ -d "$SELF/acpi" ]; then
cp "$SELF"/acpi/*.aml "$DATA"/ cp "$SELF"/acpi/*.aml "$DATA"/
for f in "$DATA"/*.aml; do QARGS+=("-acpitable" "file=$f"); done for f in "$DATA"/*.aml; do QARGS+=("-acpitable" "file=$f"); done
fi fi
# A second -cpu REPLACES the first rather than merging, so it is built from # 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 # libvirt's own generated line and never hand-written. It is incompatible
# with <hyperv>, which is why the tuned level skips it. # with <hyperv>, which is why the tuned level skips it. The brand is the
# libvirt's own -cpu comes first; a previous run's override sits at the # target's CPU for a refit, otherwise a matched host SKU.
# end, quoted because the brand has spaces, so split like a shell would. BRAND="$MODEL"; [ -n "$TARGET_MODE" ] && BRAND="$CPU_VERSION"
if [ -n "$MODEL" ]; then if [ -n "$BRAND" ]; then
python3 - "$BACKUP/$DOM.setup.xml" "$BACKUP/$DOM.nohostdev.xml" <<'STRIP_END' python3 - "$BACKUP/$DOM.setup.xml" "$BACKUP/$DOM.nohostdev.xml" <<'STRIP_END'
import io, re, sys import io, re, sys
s = io.open(sys.argv[1], encoding="utf-8").read() s = io.open(sys.argv[1], encoding="utf-8").read()
@@ -744,9 +871,32 @@ STRIP_END
import shlex, sys import shlex, sys
a = shlex.split(sys.stdin.read()) a = shlex.split(sys.stdin.read())
print(a[a.index("-cpu") + 1] if "-cpu" in a else "")') print(a[a.index("-cpu") + 1] if "-cpu" in a else "")')
if [ -n "$GEN" ] && [[ "$GEN" != *hv-* ]]; then QARGS+=("-cpu" "$GEN,model-id=$MODEL"); fi if [ -n "$GEN" ] && [[ "$GEN" != *hv-* ]]; then
# SMBIOS type 4 defaults to manufacturer "QEMU" and the machine name as CPUARG="$GEN,model-id=$BRAND"
# the version; a comma inside a QEMU option value is written twice. [ -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
# 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 case "$VENDOR:$FAMILY" in
AuthenticAMD:23) SOCK=AM4; CPUVEND="Advanced Micro Devices,, Inc.";; AuthenticAMD:23) SOCK=AM4; CPUVEND="Advanced Micro Devices,, Inc.";;
AuthenticAMD:*) SOCK=AM5; CPUVEND="Advanced Micro Devices,, Inc.";; AuthenticAMD:*) SOCK=AM5; CPUVEND="Advanced Micro Devices,, Inc.";;
@@ -760,10 +910,13 @@ print(a[a.index("-cpu") + 1] if "-cpu" in a else "")')
QARGS+=("-smbios" "type=17,loc_pfx=DIMM,bank=P0 CHANNEL A,manufacturer=$MEM_MANUFACTURER,part=$MEM_PART,serial=$MEM_SERIAL,speed=$MEM_SPEED") 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 fi
QARGS_NL="$(printf '%s\n' "${QARGS[@]}")" python3 - "$BACKUP/$DOM.setup.xml" <<'QARGS_END' fi
QARGS_NL="$(printf '%s\n' "${QARGS[@]}")" NVME_MODEL="${NVME_MODEL:-}" \
python3 - "$BACKUP/$DOM.setup.xml" <<'QARGS_END'
import io, os, re, sys import io, os, re, sys
p = sys.argv[1] p = sys.argv[1]
args = [a for a in os.environ["QARGS_NL"].split("\n") if a] 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() s = io.open(p, encoding="utf-8").read()
if "xmlns:qemu=" not in s: if "xmlns:qemu=" not in s:
s = s.replace("<domain type='kvm'>", s = s.replace("<domain type='kvm'>",
@@ -789,6 +942,14 @@ if vals or envs:
+ "".join(" %s\n" % e for e in envs) + "".join(" %s\n" % e for e in envs)
+ " </qemu:commandline>\n") + " </qemu:commandline>\n")
s = s.replace("</domain>", block + "</domain>", 1) 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("'", "&apos;") +
" </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) io.open(p, "w", encoding="utf-8").write(s)
QARGS_END QARGS_END
"${C[@]}" define "$BACKUP/$DOM.setup.xml" >/dev/null "${C[@]}" define "$BACKUP/$DOM.setup.xml" >/dev/null
@@ -814,20 +975,29 @@ if [ "$PROFILE" = full ]; then
[ "$ok" = 1 ] && echo "patched QEMU and KVM modules both in place." [ "$ok" = 1 ] && echo "patched QEMU and KVM modules both in place."
if [ -n "$MODEL" ] && [ -f /sys/module/kvm_amd/parameters/cpuid_passthrough ]; then if [ -n "$MODEL" ] && [ -f /sys/module/kvm_amd/parameters/cpuid_passthrough ]; then
echo echo
echo "The TIMER check needs CPUID passthrough, which is off by default and must be" echo "The TIMER check needs CPUID passthrough, which must be off while the guest cold"
echo "switched on AFTER the guest has booted (it hangs a booting guest). Once the" echo "boots and on once it is up. Let the hook handle that around this guest:"
echo "guest is up, on the host:" echo " sudo vm-native-cpuid enable $DOM"
echo "Or drive it by hand, after the guest has booted, on the host:"
echo " echo '$MODEL' | sudo tee /sys/module/kvm_amd/parameters/brand_string" echo " echo '$MODEL' | sudo tee /sys/module/kvm_amd/parameters/brand_string"
echo " echo Y | sudo tee /sys/module/kvm_amd/parameters/cpuid_passthrough" echo " echo Y | sudo tee /sys/module/kvm_amd/parameters/cpuid_passthrough # N again before the next boot"
echo "Switch it off again (echo N) before rebooting the guest."
fi fi
fi fi
if [ ! -e /usr/lib/udev/rules.d/99-vfio-native-vnet-offload.rules ] && [ ! -e /etc/udev/rules.d/99-vfio-native-vnet-offload.rules ]; then if [ ! -e /usr/lib/udev/rules.d/99-vfio-native-vnet-offload.rules ] && [ ! -e /etc/udev/rules.d/99-vfio-native-vnet-offload.rules ]; then
echo "NOTE: the e1000e offload udev rule is not installed. SSH into the guest will fail with" echo "NOTE: the NIC offload udev rule is not installed. SSH into the guest will fail with"
echo " 'Corrupted MAC on input' until it is:" echo " 'Corrupted MAC on input' until it is:"
echo " sudo install -Dm644 $SELF/scripts/99-vfio-native-vnet-offload.rules /etc/udev/rules.d/ && sudo udevadm control --reload-rules" echo " sudo install -Dm644 $SELF/scripts/99-vfio-native-vnet-offload.rules /etc/udev/rules.d/ && sudo udevadm control --reload-rules"
fi fi
NET=$("${C[@]}" dumpxml "$DOM" 2>/dev/null | sed -n "s/.*<source network='\([^']*\)'.*/\1/p" | head -1)
NETMTU=$([ -n "$NET" ] && "${C[@]}" net-dumpxml --inactive "$NET" 2>/dev/null | sed -n "s/.*<mtu size='\([0-9]*\)'.*/\1/p")
if [ -n "$NET" ] && [ "${NETMTU:-1500}" -lt 9000 ]; then
echo "NOTE: the guest interface asks for MTU 9000 but libvirt network '$NET' is at ${NETMTU:-1500}."
echo " Jumbo needs both ends; inbound throughput is ~5x with it. Add <mtu size='9000'/> to"
echo " the network and restart it: virsh net-edit $NET && virsh net-destroy $NET && virsh net-start $NET"
echo " Then in the guest: set the NIC's Jumbo Packet to 9014 and the interface MTU to 9000."
fi
gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || echo unknown) gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || echo unknown)
[ "$gov" = performance ] || echo "host governor is '$gov' - run: sudo cpupower frequency-set -g performance" [ "$gov" = performance ] || echo "host governor is '$gov' - run: sudo cpupower frequency-set -g performance"
+50
View File
@@ -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()