Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save daemonhorn/a34f17e103a6a403d53c878e3aeca577 to your computer and use it in GitHub Desktop.

Select an option

Save daemonhorn/a34f17e103a6a403d53c878e3aeca577 to your computer and use it in GitHub Desktop.
Patch Proxmox pve 9 to enable arm64 tpm support
From ead6a4c11602b2a78523dfe6c364b2e8eb59665c Mon Sep 17 00:00:00 2001
From: daemonhorn <daemonhorn@nullcore.com>
Date: Sat, 25 Jul 2026 12:08:08 -0400
Subject: [PATCH qemu-server] fix #4219: cfg2cmd: use tpm-tis-device for TPM on
non-x86_64 archs
QEMU's aarch64 'virt' machine has no ISA bus and only exposes the TPM
TIS interface as the sysbus device 'tpm-tis-device'. add_tpm_device()
always emitted the x86 ISA device 'tpm-tis', so any VM with 'arch:
aarch64' and a tpmstate0 configured failed to start with:
qemu-system-aarch64: -device tpm-tis,tpmdev=tpmdev: 'tpm-tis' is not
a valid device model name
Pass the already-resolved $arch through to add_tpm_device() and pick
'tpm-tis' for x86_64, 'tpm-tis-device' otherwise, as suggested by
Thomas Lamprecht in bug #4219.
Adds a cfg2cmd regression test for an aarch64 VM with tpmstate0
configured.
This is a resubmission of a similar fix sent to this list in January
2025 ("cfg2cmd: use tpm-tis and tpm-tis-device depending on the arch",
Hannes Duerr), which received no reply and was not applied.
Tested on real hardware: an aarch64 Linux guest boots successfully
with a v2.0 TPM state attached under PVE 9 with this patch applied.
Not yet tested with a Windows aarch64 guest; even with the correct
device model, upstream reports (stefanberger/swtpm#493) suggest
Windows-on-ARM64 guests may still fail to fully initialize the TPM.
Signed-off-by: daemonhorn <daemonhorn@nullcore.com>
---
src/PVE/QemuServer.pm | 10 +++--
src/test/cfg2cmd/aarch64/simple-arm-tpm.conf | 18 ++++++++
.../cfg2cmd/aarch64/simple-arm-tpm.conf.cmd | 44 +++++++++++++++++++
3 files changed, 69 insertions(+), 3 deletions(-)
create mode 100644 src/test/cfg2cmd/aarch64/simple-arm-tpm.conf
create mode 100644 src/test/cfg2cmd/aarch64/simple-arm-tpm.conf.cmd
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 9aec7f9..0c39eea 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -2840,15 +2840,19 @@ sub get_tpm_paths {
}
sub add_tpm_device {
- my ($vmid, $devices, $conf) = @_;
+ my ($vmid, $devices, $conf, $arch) = @_;
return if !$conf->{tpmstate0};
my $paths = get_tpm_paths($vmid);
+ # the ISA-bus 'tpm-tis' device is x86-only, non-x86 machines (e.g. aarch64's
+ # 'virt' machine) expose the TIS interface as a sysbus device instead
+ my $tpm_device = $arch eq 'x86_64' ? 'tpm-tis' : 'tpm-tis-device';
+
push @$devices, "-chardev", "socket,id=tpmchar,path=$paths->{socket}";
push @$devices, "-tpmdev", "emulator,id=tpmdev,chardev=tpmchar";
- push @$devices, "-device", "tpm-tis,tpmdev=tpmdev";
+ push @$devices, "-device", "$tpm_device,tpmdev=tpmdev";
}
sub start_swtpm {
@@ -3353,7 +3357,7 @@ sub config_to_command {
# Add a TPM only if the VM is not a template,
# to support backing up template VMs even if the TPM disk is write-protected.
- add_tpm_device($vmid, $devices, $conf) if !$is_template;
+ add_tpm_device($vmid, $devices, $conf, $arch) if !$is_template;
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
diff --git a/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf b/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf
new file mode 100644
index 0000000..1526a46
--- /dev/null
+++ b/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf
@@ -0,0 +1,18 @@
+# TEST: aarch64 VM with a TPM device uses the sysbus tpm-tis-device model
+arch: aarch64
+bootdisk: scsi0
+bios: ovmf
+cores: 3
+ide2: none,media=cdrom
+memory: 768
+name: simple
+net0: virtio=A2:C0:43:77:08:A0,bridge=vmbr0
+numa: 0
+ostype: l26
+scsi0: local:8006/vm-8006-disk-0.qcow2,discard=on,size=104858K
+scsihw: virtio-scsi-pci
+smbios1: uuid=7b10d7af-b932-4c66-b2c3-3996152ec465
+sockets: 1
+vmgenid: c773c261-d800-4348-9f5d-167fadd53cf8
+efidisk0: local:8006/vm-8006-disk-1.qcow2,efitype=4m
+tpmstate0: local:8006/vm-8006-disk-2.qcow2,size=4M,version=v2.0
diff --git a/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf.cmd b/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf.cmd
new file mode 100644
index 0000000..5bd8f17
--- /dev/null
+++ b/src/test/cfg2cmd/aarch64/simple-arm-tpm.conf.cmd
@@ -0,0 +1,44 @@
+/usr/bin/qemu-system-aarch64 \
+ -id 8006 \
+ -name simple \
+ -no-shutdown \
+ -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \
+ -mon 'chardev=qmp,mode=control' \
+ -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect-ms=5000' \
+ -mon 'chardev=qmp-event,mode=control' \
+ -pidfile /var/run/qemu-server/8006.pid \
+ -daemonize \
+ -smbios 'type=1,uuid=7b10d7af-b932-4c66-b2c3-3996152ec465' \
+ -object '{"id":"throttle-drive-efidisk0","limits":{},"qom-type":"throttle-group"}' \
+ -blockdev '{"driver":"raw","file":{"driver":"file","filename":"/usr/share/pve-edk2-firmware//AAVMF_CODE.fd"},"node-name":"pflash0","read-only":true}' \
+ -blockdev '{"detect-zeroes":"on","discard":"ignore","driver":"throttle","file":{"cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"qcow2","file":{"aio":"io_uring","cache":{"direct":false,"no-flush":false},"detect-zeroes":"on","discard":"ignore","driver":"file","filename":"/var/lib/vz/images/8006/vm-8006-disk-1.qcow2","node-name":"e4abf08bba72d3a54b87a58d2f50906","read-only":false},"node-name":"f4abf08bba72d3a54b87a58d2f50906","read-only":false},"node-name":"drive-efidisk0","read-only":false,"throttle-group":"throttle-drive-efidisk0"}' \
+ -smp '3,sockets=1,cores=3,maxcpus=3' \
+ -nodefaults \
+ -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \
+ -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \
+ -cpu cortex-a57 \
+ -m 768 \
+ -object '{"id":"throttle-drive-scsi0","limits":{},"qom-type":"throttle-group"}' \
+ -device 'pci-bridge,id=pci.1,chassis_nr=1,bus=pcie.0,addr=0x1e' \
+ -device 'pci-bridge,id=pci.2,chassis_nr=2,bus=pcie.0,addr=0x1f' \
+ -device 'vmgenid,guid=c773c261-d800-4348-9f5d-167fadd53cf8' \
+ -device 'usb-ehci,id=ehci,bus=pcie.0,addr=0x1' \
+ -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \
+ -device 'usb-kbd,id=keyboard,bus=ehci.0,port=2' \
+ -chardev 'socket,id=tpmchar,path=/var/run/qemu-server/8006.swtpm' \
+ -tpmdev 'emulator,id=tpmdev,chardev=tpmchar' \
+ -device 'tpm-tis-device,tpmdev=tpmdev' \
+ -device 'virtio-gpu,id=vga,bus=pcie.0,addr=0x2' \
+ -device 'virtio-serial,id=spice,bus=pcie.0,addr=0x9' \
+ -chardev 'spicevmc,id=vdagent,name=vdagent' \
+ -device 'virtserialport,chardev=vdagent,name=com.redhat.spice.0' \
+ -spice 'tls-port=61000,addr=127.0.0.1,tls-ciphers=HIGH,seamless-migration=on' \
+ -device 'virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x3,free-page-reporting=on' \
+ -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \
+ -device 'ide-cd,bus=ide.1,unit=0,id=ide2,bootindex=200' \
+ -device 'virtio-scsi-pci,id=scsihw0,bus=pcie.0,addr=0x5' \
+ -blockdev '{"detect-zeroes":"unmap","discard":"unmap","driver":"throttle","file":{"cache":{"direct":true,"no-flush":false},"detect-zeroes":"unmap","discard":"unmap","driver":"qcow2","file":{"aio":"io_uring","cache":{"direct":true,"no-flush":false},"detect-zeroes":"unmap","discard":"unmap","driver":"file","filename":"/var/lib/vz/images/8006/vm-8006-disk-0.qcow2","node-name":"ecd04be4259153b8293415fefa2a84c","read-only":false},"node-name":"fcd04be4259153b8293415fefa2a84c","read-only":false},"node-name":"drive-scsi0","read-only":false,"throttle-group":"throttle-drive-scsi0"}' \
+ -device 'scsi-hd,bus=scsihw0.0,channel=0,scsi-id=0,lun=0,drive=drive-scsi0,id=scsi0,device_id=drive-scsi0,bootindex=100,write-cache=on' \
+ -netdev 'type=tap,id=net0,ifname=tap8006i0,script=/usr/libexec/qemu-server/pve-bridge,downscript=/usr/libexec/qemu-server/pve-bridgedown' \
+ -device 'virtio-net-pci,mac=A2:C0:43:77:08:A0,netdev=net0,bus=pcie.0,addr=0x12,id=net0,rx_queue_size=1024,tx_queue_size=256,bootindex=300,host_mtu=1500,host_tunnel=off' \
+ -machine 'pflash0=pflash0,pflash1=drive-efidisk0,accel=tcg,type=virt+pve0'
--
2.47.3

PVE 9 arm64 TPM fix

Problem

Proxmox VE 9's qemu-server hardcodes the x86 ISA tpm-tis device model in add_tpm_device() (src/PVE/QemuServer.pm). QEMU's aarch64 virt machine has no ISA bus and only exposes TPM as the sysbus device tpm-tis-device. As a result, any VM with arch: aarch64 and a tpmstate0 configured fails to start:

qemu-system-aarch64: -device tpm-tis,tpmdev=tpmdev: 'tpm-tis' is not a valid device model name

This is not a configuration gap — no qm/PVE setting works around it, it requires a source patch. Tracked upstream as Bugzilla #4219 (open since 2022-09-15, status "MORE INFO NEEDED"). A community fix was sent to pve-devel in January 2025 ("cfg2cmd: use tpm-tis and tpm-tis-device depending on the arch") but got no replies and was never merged.

Fix

tpm-arm64.patch — applies against qemu-server v9.2.1 (proxmox/qemu-server commit 601c77f, the tip as of 2026-07-24):

  • add_tpm_device() now takes an $arch parameter and picks tpm-tis for x86_64 / tpm-tis-device otherwise.
  • Adds a regression test fixture, src/test/cfg2cmd/aarch64/simple-arm-tpm.conf (+ .conf.cmd snapshot), covering an aarch64 VM with tpmstate0 configured.

Verified:

  • make deb builds cleanly (only pre-existing cosmetic lintian warnings about man page line-wrapping, unrelated to this change).
  • Full cfg2cmd test suite (102 tests) passes.
  • New aarch64 test emits -device 'tpm-tis-device,tpmdev=tpmdev'.
  • Existing x86_64/q35 TPM tests (efi-secboot-and-tpm.conf, efi-secboot-and-tpm-q35.conf) still emit -device 'tpm-tis,tpmdev=tpmdev' — no regression.

Confirmed on real hardware (2026-07-25): installed on a live PVE 9 box, an aarch64 Linux guest boots successfully with a v2.0 TPM state attached. Not yet tested with a Windows aarch64 guest — even with the correct device model, upstream reports (swtpm project issue #493) suggest Windows-on-ARM64 guests may still fail to fully initialize the TPM.

Files

  • tpm-arm64.patch.old — the original unified diff (source fix + test fixture only, no commit metadata). Superseded by the item below; kept for reference.
  • patch-for-upstream/0001-fix-4219-cfg2cmd-use-tpm-tis-device-for-TPM-on-non-x.patch — proper git format-patch output (commit message, Signed-off-by, ready to mail) for submitting upstream. Apply with git am <file> inside a qemu-server v9.2.1 checkout.
  • qemu-server_9.2.1_amd64.deb — prebuilt package with the fix applied. sha256sum: 4809923bac162771145705ab7237a995b7ea76f788aebfa63c1e5245e636ad49

Submitting upstream

Not yet sent. Two things to sort out first:

  1. CLA: Proxmox requires a signed Contributor License Agreement before any patch can be merged — send the Individual CLA to office@proxmox.com if not already on file. Status as of this writing: unconfirmed.

  2. Send the patch via git send-email (required — plain mail clients mangle patch formatting) from wherever you have SMTP/mail configured:

    git send-email --to=pve-devel@lists.proxmox.com \
      patch-for-upstream/0001-fix-4219-cfg2cmd-use-tpm-tis-device-for-TPM-on-non-x.patch
    

    Signed-off-by / author identity used: daemonhorn <daemonhorn@nullcore.com> (matches this machine's git config — update the patch first with git commit --amend if you want a different identity before sending).

Install (on the PVE 9 box)

sha256sum qemu-server_9.2.1_amd64.deb   # confirm it matches the hash above

dpkg -l qemu-server                                          # note current version
apt-get install --reinstall --download-only qemu-server      # cache the official .deb for revert
cp /usr/share/perl5/PVE/QemuServer.pm ~/QemuServer.pm.orig    # extra safety copy

apt install ./qemu-server_9.2.1_amd64.deb
apt-mark hold qemu-server

# qm CLI forks fresh perl each run and picks up the patch immediately.
# Restart these only if testing via GUI/API:
systemctl restart pvedaemon pveproxy pvestatd

Avoid installing while VMs are actively starting/stopping/migrating.

Test:

qm set <vmid> --arch aarch64 --bios ovmf --tpmstate0 <storage>:1,version=v2.0
qm start <vmid>

Revert

apt-mark unhold qemu-server
apt-get install --reinstall qemu-server     # restores stock binary from cache
dpkg -V qemu-server                         # should report no discrepancies
systemctl restart pvedaemon pveproxy pvestatd   # only if you restarted them above
rm -f ~/QemuServer.pm.orig
@daemonhorn

Copy link
Copy Markdown
Author

Pre-built Debian package that should match hash above: https://drive.google.com/file/d/1qGKpCZfRI99XYmz_6lgP8kTSpu9gkOCv/view?usp=sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment