* [PATCH v5 01/10] hw/tpm: Add TPM CRB chunking fields
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 02/10] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
` (8 subsequent siblings)
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Arun Menon <armenon@redhat.com>
- Add new fields to the CRB Interface Identifier and the CRB
Control Start registers.
- CRB_CTRL_START now has 2 new settings, that can be toggled using the
nextChunk and crbRspRetry bits.
- CapCRBChunk bit (10) was Reserved1 previously. The field is reused in
this revision of the specification. Refer to section 6.4.2.2 of [1]
- Add hw_compat global property called cap-chunk because the chunking
feature is only supported for machine type 11.1 and higher.
[1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_Pub.pdf
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/core/machine.c | 4 +++-
hw/tpm/tpm_crb.c | 6 ++++++
include/hw/acpi/tpm.h | 5 ++++-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 1abc8ae737..6d27cf69a2 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -38,7 +38,9 @@
#include "hw/acpi/generic_event_device.h"
#include "qemu/audio.h"
-GlobalProperty hw_compat_11_0[] = {};
+GlobalProperty hw_compat_11_0[] = {
+ { "tpm-crb", "cap-chunk", "off"},
+};
const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
GlobalProperty hw_compat_10_2[] = {
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 8723536f93..9555b2c62d 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -45,6 +45,8 @@ struct CRBState {
bool ppi_enabled;
TPMPPI ppi;
+
+ bool cap_chunk;
};
typedef struct CRBState CRBState;
@@ -59,6 +61,7 @@ DECLARE_INSTANCE_CHECKER(CRBState, CRB,
#define CRB_INTF_CAP_FIFO_NOT_SUPPORTED 0b0
#define CRB_INTF_CAP_CRB_SUPPORTED 0b1
#define CRB_INTF_IF_SELECTOR_CRB 0b1
+#define CRB_INTF_CAP_CRB_CHUNK 0b1
#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER)
@@ -229,6 +232,7 @@ static const VMStateDescription vmstate_tpm_crb = {
static const Property tpm_crb_properties[] = {
DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
+ DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
};
static void tpm_crb_reset(void *dev)
@@ -262,6 +266,8 @@ static void tpm_crb_reset(void *dev)
CapCRB, CRB_INTF_CAP_CRB_SUPPORTED);
ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
InterfaceSelector, CRB_INTF_IF_SELECTOR_CRB);
+ ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
+ CapCRBChunk, s->cap_chunk ? CRB_INTF_CAP_CRB_CHUNK : 0);
ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID,
RID, 0b0000);
ARRAY_FIELD_DP32(s->regs, CRB_INTF_ID2,
diff --git a/include/hw/acpi/tpm.h b/include/hw/acpi/tpm.h
index d2bf6637c5..6eece21397 100644
--- a/include/hw/acpi/tpm.h
+++ b/include/hw/acpi/tpm.h
@@ -149,7 +149,7 @@ REG32(CRB_INTF_ID, 0x30)
FIELD(CRB_INTF_ID, InterfaceVersion, 4, 4)
FIELD(CRB_INTF_ID, CapLocality, 8, 1)
FIELD(CRB_INTF_ID, CapCRBIdleBypass, 9, 1)
- FIELD(CRB_INTF_ID, Reserved1, 10, 1)
+ FIELD(CRB_INTF_ID, CapCRBChunk, 10, 1)
FIELD(CRB_INTF_ID, CapDataXferSizeSupport, 11, 2)
FIELD(CRB_INTF_ID, CapFIFO, 13, 1)
FIELD(CRB_INTF_ID, CapCRB, 14, 1)
@@ -168,6 +168,9 @@ REG32(CRB_CTRL_STS, 0x44)
FIELD(CRB_CTRL_STS, tpmIdle, 1, 1)
REG32(CRB_CTRL_CANCEL, 0x48)
REG32(CRB_CTRL_START, 0x4C)
+ FIELD(CRB_CTRL_START, Start, 0, 1)
+ FIELD(CRB_CTRL_START, crbRspRetry, 1, 1)
+ FIELD(CRB_CTRL_START, nextChunk, 2, 1)
REG32(CRB_INT_ENABLED, 0x50)
REG32(CRB_INT_STS, 0x54)
REG32(CRB_CTRL_CMD_SIZE, 0x58)
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 02/10] hw/tpm: Refactor CRB_CTRL_START register access
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-04-22 10:30 ` [PATCH v5 01/10] hw/tpm: Add TPM CRB chunking fields Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking Arun Menon
` (7 subsequent siblings)
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Arun Menon <armenon@redhat.com>
Replace manual bitwise operations with ARRAY_FIELD_DP32 macros
No functional changes.
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/tpm/tpm_crb.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 9555b2c62d..7edfbf8f8a 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -147,7 +147,7 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
tpm_crb_get_active_locty(s) == locty) {
void *mem = memory_region_get_ram_ptr(&s->cmdmem);
- s->regs[R_CRB_CTRL_START] |= CRB_START_INVOKE;
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 1);
s->cmd = (TPMBackendCmd) {
.in = mem,
.in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size),
@@ -196,7 +196,7 @@ static void tpm_crb_request_completed(TPMIf *ti, int ret)
{
CRBState *s = CRB(ti);
- s->regs[R_CRB_CTRL_START] &= ~CRB_START_INVOKE;
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 0);
if (ret != 0) {
ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
tpmSts, 1); /* fatal error */
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-04-22 10:30 ` [PATCH v5 01/10] hw/tpm: Add TPM CRB chunking fields Arun Menon
2026-04-22 10:30 ` [PATCH v5 02/10] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 12:55 ` Stefan Berger
2026-04-29 14:16 ` Stefan Berger
2026-04-22 10:30 ` [PATCH v5 04/10] hw/tpm: Implement TPM CRB chunking logic Arun Menon
` (6 subsequent siblings)
9 siblings, 2 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon
From: Arun Menon <armenon@redhat.com>
- Introduce GByteArray buffers to hold the command request and response
data during chunked TPM CRB transactions.
- Add helper function to clean them.
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/tpm/tpm_crb.c | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 7edfbf8f8a..e7ec9d0679 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -38,10 +38,13 @@ struct CRBState {
TPMBackend *tpmbe;
TPMBackendCmd cmd;
uint32_t regs[TPM_CRB_R_MAX];
+ size_t be_buffer_size;
MemoryRegion mmio;
MemoryRegion cmdmem;
- size_t be_buffer_size;
+ GByteArray *command_buffer;
+ GByteArray *response_buffer;
+ uint32_t response_offset;
bool ppi_enabled;
TPMPPI ppi;
@@ -87,6 +90,13 @@ enum crb_cancel {
#define TPM_CRB_NO_LOCALITY 0xff
+static void tpm_crb_clear_internal_buffers(CRBState *s)
+{
+ g_byte_array_set_size(s->response_buffer, 0);
+ g_byte_array_set_size(s->command_buffer, 0);
+ s->response_offset = 0;
+}
+
static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr,
unsigned size)
{
@@ -136,9 +146,11 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
}
break;
case A_CRB_CTRL_CANCEL:
- if (val == CRB_CANCEL_INVOKE &&
- s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
- tpm_backend_cancel_cmd(s->tpmbe);
+ if (val == CRB_CANCEL_INVOKE) {
+ if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
+ tpm_backend_cancel_cmd(s->tpmbe);
+ }
+ tpm_crb_clear_internal_buffers(s);
}
break;
case A_CRB_CTRL_START:
@@ -243,6 +255,7 @@ static void tpm_crb_reset(void *dev)
tpm_ppi_reset(&s->ppi);
}
tpm_backend_reset(s->tpmbe);
+ tpm_crb_clear_internal_buffers(s);
memset(s->regs, 0, sizeof(s->regs));
@@ -309,6 +322,9 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion(get_system_memory(),
TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem);
+ s->command_buffer = g_byte_array_new();
+ s->response_buffer = g_byte_array_new();
+
if (s->ppi_enabled) {
tpm_ppi_init(&s->ppi, get_system_memory(),
TPM_PPI_ADDR_BASE, OBJECT(s));
@@ -321,12 +337,21 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
}
}
+static void tpm_crb_unrealize(DeviceState *dev)
+{
+ CRBState *s = CRB(dev);
+
+ g_clear_pointer(&s->command_buffer, g_byte_array_unref);
+ g_clear_pointer(&s->response_buffer, g_byte_array_unref);
+}
+
static void tpm_crb_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
TPMIfClass *tc = TPM_IF_CLASS(klass);
dc->realize = tpm_crb_realize;
+ dc->unrealize = tpm_crb_unrealize;
device_class_set_props(dc, tpm_crb_properties);
dc->vmsd = &vmstate_tpm_crb;
dc->user_creatable = true;
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking
2026-04-22 10:30 ` [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking Arun Menon
@ 2026-04-22 12:55 ` Stefan Berger
2026-04-29 14:16 ` Stefan Berger
1 sibling, 0 replies; 24+ messages in thread
From: Stefan Berger @ 2026-04-22 12:55 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - Introduce GByteArray buffers to hold the command request and response
> data during chunked TPM CRB transactions.
> - Add helper function to clean them.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> hw/tpm/tpm_crb.c | 33 +++++++++++++++++++++++++++++----
> 1 file changed, 29 insertions(+), 4 deletions(-)
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index 7edfbf8f8a..e7ec9d0679 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -38,10 +38,13 @@ struct CRBState {
> TPMBackend *tpmbe;
> TPMBackendCmd cmd;
> uint32_t regs[TPM_CRB_R_MAX];
> + size_t be_buffer_size;
> MemoryRegion mmio;
> MemoryRegion cmdmem;
>
> - size_t be_buffer_size;
> + GByteArray *command_buffer;
> + GByteArray *response_buffer;
> + uint32_t response_offset;
>
> bool ppi_enabled;
> TPMPPI ppi;
> @@ -87,6 +90,13 @@ enum crb_cancel {
>
> #define TPM_CRB_NO_LOCALITY 0xff
>
> +static void tpm_crb_clear_internal_buffers(CRBState *s)
> +{
> + g_byte_array_set_size(s->response_buffer, 0);
> + g_byte_array_set_size(s->command_buffer, 0);
> + s->response_offset = 0;
> +}
> +
> static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr,
> unsigned size)
> {
> @@ -136,9 +146,11 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
> }
> break;
> case A_CRB_CTRL_CANCEL:
> - if (val == CRB_CANCEL_INVOKE &&
> - s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
> - tpm_backend_cancel_cmd(s->tpmbe);
> + if (val == CRB_CANCEL_INVOKE) {
> + if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
> + tpm_backend_cancel_cmd(s->tpmbe);
> + }
> + tpm_crb_clear_internal_buffers(s);
> }
> break;
> case A_CRB_CTRL_START:
> @@ -243,6 +255,7 @@ static void tpm_crb_reset(void *dev)
> tpm_ppi_reset(&s->ppi);
> }
> tpm_backend_reset(s->tpmbe);
> + tpm_crb_clear_internal_buffers(s);
>
> memset(s->regs, 0, sizeof(s->regs));
>
> @@ -309,6 +322,9 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> memory_region_add_subregion(get_system_memory(),
> TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem);
>
> + s->command_buffer = g_byte_array_new();
> + s->response_buffer = g_byte_array_new();
> +
> if (s->ppi_enabled) {
> tpm_ppi_init(&s->ppi, get_system_memory(),
> TPM_PPI_ADDR_BASE, OBJECT(s));
> @@ -321,12 +337,21 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> }
> }
>
> +static void tpm_crb_unrealize(DeviceState *dev)
> +{
> + CRBState *s = CRB(dev);
> +
> + g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> + g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> +}
> +
> static void tpm_crb_class_init(ObjectClass *klass, const void *data)
> {
> DeviceClass *dc = DEVICE_CLASS(klass);
> TPMIfClass *tc = TPM_IF_CLASS(klass);
>
> dc->realize = tpm_crb_realize;
> + dc->unrealize = tpm_crb_unrealize;
> device_class_set_props(dc, tpm_crb_properties);
> dc->vmsd = &vmstate_tpm_crb;
> dc->user_creatable = true;
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking
2026-04-22 10:30 ` [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking Arun Menon
2026-04-22 12:55 ` Stefan Berger
@ 2026-04-29 14:16 ` Stefan Berger
1 sibling, 0 replies; 24+ messages in thread
From: Stefan Berger @ 2026-04-29 14:16 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - Introduce GByteArray buffers to hold the command request and response
> data during chunked TPM CRB transactions.
> - Add helper function to clean them.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
> hw/tpm/tpm_crb.c | 33 +++++++++++++++++++++++++++++----
> 1 file changed, 29 insertions(+), 4 deletions(-)
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index 7edfbf8f8a..e7ec9d0679 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -38,10 +38,13 @@ struct CRBState {
> TPMBackend *tpmbe;
> TPMBackendCmd cmd;
> uint32_t regs[TPM_CRB_R_MAX];
> + size_t be_buffer_size;
> MemoryRegion mmio;
> MemoryRegion cmdmem;
>
> - size_t be_buffer_size;
> + GByteArray *command_buffer;
> + GByteArray *response_buffer;
> + uint32_t response_offset;
>
> bool ppi_enabled;
> TPMPPI ppi;
> @@ -87,6 +90,13 @@ enum crb_cancel {
>
> #define TPM_CRB_NO_LOCALITY 0xff
>
> +static void tpm_crb_clear_internal_buffers(CRBState *s)
> +{
> + g_byte_array_set_size(s->response_buffer, 0);
> + g_byte_array_set_size(s->command_buffer, 0);
> + s->response_offset = 0;
> +}
> +
> static uint64_t tpm_crb_mmio_read(void *opaque, hwaddr addr,
> unsigned size)
> {
> @@ -136,9 +146,11 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
> }
> break;
> case A_CRB_CTRL_CANCEL:
> - if (val == CRB_CANCEL_INVOKE &&
> - s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
> - tpm_backend_cancel_cmd(s->tpmbe);
> + if (val == CRB_CANCEL_INVOKE) {
> + if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
> + tpm_backend_cancel_cmd(s->tpmbe);
> + }
> + tpm_crb_clear_internal_buffers(s);
> }
> break;
> case A_CRB_CTRL_START:
> @@ -243,6 +255,7 @@ static void tpm_crb_reset(void *dev)
> tpm_ppi_reset(&s->ppi);
> }
> tpm_backend_reset(s->tpmbe);
> + tpm_crb_clear_internal_buffers(s);
>
> memset(s->regs, 0, sizeof(s->regs));
>
> @@ -309,6 +322,9 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> memory_region_add_subregion(get_system_memory(),
> TPM_CRB_ADDR_BASE + sizeof(s->regs), &s->cmdmem);
>
> + s->command_buffer = g_byte_array_new();
> + s->response_buffer = g_byte_array_new();
> +
> if (s->ppi_enabled) {
> tpm_ppi_init(&s->ppi, get_system_memory(),
> TPM_PPI_ADDR_BASE, OBJECT(s));
> @@ -321,12 +337,21 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> }
> }
>
> +static void tpm_crb_unrealize(DeviceState *dev)
> +{
> + CRBState *s = CRB(dev);
> +
> + g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> + g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> +}
> +
> static void tpm_crb_class_init(ObjectClass *klass, const void *data)
> {
> DeviceClass *dc = DEVICE_CLASS(klass);
> TPMIfClass *tc = TPM_IF_CLASS(klass);
>
> dc->realize = tpm_crb_realize;
> + dc->unrealize = tpm_crb_unrealize;
> device_class_set_props(dc, tpm_crb_properties);
> dc->vmsd = &vmstate_tpm_crb;
> dc->user_creatable = true;
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v5 04/10] hw/tpm: Implement TPM CRB chunking logic
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (2 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 03/10] hw/tpm: Add internal buffer state for chunking Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 05/10] test/qtest: Add test for tpm crb chunking Arun Menon
` (5 subsequent siblings)
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Arun Menon <armenon@redhat.com>
- Add logic to populate internal TPM command request and response
buffers and to toggle the control registers after each operation.
- The chunk size is limited to CRB_CTRL_CMD_SIZE which is
(TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER). This comes out as 3968 bytes
(4096 - 128 or 0x1000 - 0x80), because 128 bytes are reserved for
control and status registers. In other words, only 3968 bytes are
available for the TPM data.
- With this feature, guests can send commands larger than 3968 bytes.
- Refer section 6.5.3.9 of [1] for implementation details.
[1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_Pub.pdf
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/tpm/tpm_crb.c | 154 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 137 insertions(+), 17 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index e7ec9d0679..29370d6f49 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -17,6 +17,7 @@
#include "qemu/osdep.h"
#include "qemu/module.h"
+#include "qemu/error-report.h"
#include "qapi/error.h"
#include "system/address-spaces.h"
#include "hw/core/qdev-properties.h"
@@ -67,6 +68,7 @@ DECLARE_INSTANCE_CHECKER(CRBState, CRB,
#define CRB_INTF_CAP_CRB_CHUNK 0b1
#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER)
+#define TPM_HEADER_SIZE 10
enum crb_loc_ctrl {
CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0),
@@ -82,6 +84,8 @@ enum crb_ctrl_req {
enum crb_start {
CRB_START_INVOKE = BIT(0),
+ CRB_START_RSP_RETRY = BIT(1),
+ CRB_START_NEXT_CHUNK = BIT(2),
};
enum crb_cancel {
@@ -124,6 +128,69 @@ static uint8_t tpm_crb_get_active_locty(CRBState *s)
return ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, activeLocality);
}
+static bool tpm_crb_append_command_request(CRBState *s)
+{
+ /*
+ * The linux guest writes the TPM command to the MMIO region in chunks.
+ * This function appends a chunk from the MMIO region to internal
+ * command_buffer.
+ */
+ void *mem = memory_region_get_ram_ptr(&s->cmdmem);
+ uint32_t to_copy = 0;
+ uint32_t total_request_size = 0;
+
+ /*
+ * The initial call extracts the total TPM command size
+ * from its header. For the subsequent calls, the data already
+ * appended in the command_buffer is used to calculate the total
+ * size, as its header stays the same.
+ */
+ if (s->command_buffer->len == 0) {
+ total_request_size = tpm_cmd_get_size(mem);
+ if (total_request_size < TPM_HEADER_SIZE) {
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, tpmSts, 1);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
+ tpm_crb_clear_internal_buffers(s);
+ error_report("Command size %" PRIu32 " less than "
+ "TPM header size %" PRIu32,
+ total_request_size, (uint32_t)TPM_HEADER_SIZE);
+ return false;
+ }
+ } else {
+ total_request_size = tpm_cmd_get_size(s->command_buffer->data);
+ }
+ total_request_size = MIN(total_request_size, s->be_buffer_size);
+
+ if (total_request_size > s->command_buffer->len) {
+ uint32_t remaining = total_request_size - s->command_buffer->len;
+ to_copy = MIN(remaining, CRB_CTRL_CMD_SIZE);
+ g_byte_array_append(s->command_buffer, (guint8 *)mem, to_copy);
+ }
+ return true;
+}
+
+static void tpm_crb_fill_command_response(CRBState *s)
+{
+ /*
+ * Response from the tpm backend will be stored in the internal
+ * response_buffer. This function will serve that accumulated response
+ * to the linux guest in chunks by writing it back to MMIO region.
+ */
+ void *mem = memory_region_get_ram_ptr(&s->cmdmem);
+ uint32_t remaining = s->response_buffer->len - s->response_offset;
+ uint32_t to_copy = MIN(CRB_CTRL_CMD_SIZE, remaining);
+
+ memcpy(mem, s->response_buffer->data + s->response_offset, to_copy);
+
+ if (to_copy < CRB_CTRL_CMD_SIZE) {
+ memset((guint8 *)mem + to_copy, 0, CRB_CTRL_CMD_SIZE - to_copy);
+ }
+
+ s->response_offset += to_copy;
+ memory_region_set_dirty(&s->cmdmem, 0, CRB_CTRL_CMD_SIZE);
+}
+
static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
@@ -154,20 +221,61 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
}
break;
case A_CRB_CTRL_START:
- if (val == CRB_START_INVOKE &&
- !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) &&
- tpm_crb_get_active_locty(s) == locty) {
- void *mem = memory_region_get_ram_ptr(&s->cmdmem);
-
- ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 1);
- s->cmd = (TPMBackendCmd) {
- .in = mem,
- .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size),
- .out = mem,
- .out_len = s->be_buffer_size,
- };
-
- tpm_backend_deliver_request(s->tpmbe, &s->cmd);
+ if (tpm_crb_get_active_locty(s) != locty) {
+ break;
+ }
+ if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
+ /*
+ * Backend TPM is busy processing a request.
+ */
+ break;
+ }
+ if (val & CRB_START_INVOKE) {
+ if (!(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE)) {
+ if (!tpm_crb_append_command_request(s)) {
+ break;
+ }
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 1);
+ g_byte_array_set_size(s->response_buffer, s->be_buffer_size);
+ s->cmd = (TPMBackendCmd) {
+ .in = s->command_buffer->data,
+ .in_len = s->command_buffer->len,
+ .out = s->response_buffer->data,
+ .out_len = s->response_buffer->len,
+ };
+ tpm_backend_deliver_request(s->tpmbe, &s->cmd);
+ }
+ } else if (val & CRB_START_NEXT_CHUNK) {
+ if (!s->cap_chunk) {
+ break;
+ }
+ /*
+ * nextChunk is used both while sending and receiving data.
+ * To distinguish between the two, response_buffer is checked.
+ * If it does not have data, then that means we have not yet
+ * sent the command to the tpm backend, and therefore call
+ * tpm_crb_append_command_request().
+ */
+ if (s->response_buffer->len > 0 &&
+ s->response_offset < s->response_buffer->len) {
+ tpm_crb_fill_command_response(s);
+ } else {
+ if (!tpm_crb_append_command_request(s)) {
+ break;
+ }
+ }
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
+ } else if (val & CRB_START_RSP_RETRY) {
+ if (!s->cap_chunk) {
+ break;
+ }
+ if (s->response_buffer->len > 0) {
+ trace_tpm_crb_mmio_write(addr, size, val);
+ s->response_offset = 0;
+ tpm_crb_fill_command_response(s);
+ }
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, crbRspRetry, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
}
break;
case A_CRB_LOC_CTRL:
@@ -212,8 +320,21 @@ static void tpm_crb_request_completed(TPMIf *ti, int ret)
if (ret != 0) {
ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
tpmSts, 1); /* fatal error */
+ tpm_crb_clear_internal_buffers(s);
+ } else {
+ uint32_t actual_resp_size = tpm_cmd_get_size(s->response_buffer->data);
+ uint32_t total_resp_size = MIN(actual_resp_size, s->be_buffer_size);
+ g_byte_array_set_size(s->response_buffer, total_resp_size);
+ s->response_offset = 0;
}
- memory_region_set_dirty(&s->cmdmem, 0, CRB_CTRL_CMD_SIZE);
+ /*
+ * Send the first chunk. Subsequent chunks will be sent
+ * on receiving nextChunk from the guest
+ */
+ tpm_crb_fill_command_response(s);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, crbRspRetry, 0);
+ g_byte_array_set_size(s->command_buffer, 0);
}
static enum TPMVersion tpm_crb_get_version(TPMIf *ti)
@@ -291,8 +412,7 @@ static void tpm_crb_reset(void *dev)
s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE;
s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
- s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe),
- CRB_CTRL_CMD_SIZE);
+ s->be_buffer_size = tpm_backend_get_buffer_size(s->tpmbe);
if (tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size) < 0) {
exit(1);
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 05/10] test/qtest: Add test for tpm crb chunking
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (3 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 04/10] hw/tpm: Implement TPM CRB chunking logic Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-29 14:26 ` Stefan Berger
2026-04-22 10:30 ` [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
` (4 subsequent siblings)
9 siblings, 1 reply; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon
From: Arun Menon <armenon@redhat.com>
- New test case added to the swtpm test. Data is written and read from
the buffer in chunks.
- The chunk size is dynamically calculated by reading the
CRB_CTRL_CMD_SIZE address. This can be changed manually to test.
- Add a helper function tpm_wait_till_bit_clear()
- Note that this commit does not yet exercise the chunked read/write
logic, as current transfer sizes remain small. Testing for large
transfers is introduced in a subsequent patch: 'tests: Use ML-DSA-87
operations to cause large TPM transfers with CRB'
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
tests/qtest/tpm-crb-swtpm-test.c | 10 +++
tests/qtest/tpm-util.c | 110 ++++++++++++++++++++++++++-----
tests/qtest/tpm-util.h | 5 ++
3 files changed, 110 insertions(+), 15 deletions(-)
diff --git a/tests/qtest/tpm-crb-swtpm-test.c b/tests/qtest/tpm-crb-swtpm-test.c
index ffeb1c396b..050c7b0c1f 100644
--- a/tests/qtest/tpm-crb-swtpm-test.c
+++ b/tests/qtest/tpm-crb-swtpm-test.c
@@ -33,6 +33,14 @@ static void tpm_crb_swtpm_test(const void *data)
"tpm-crb", NULL);
}
+static void tpm_crb_chunk_swtpm_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_test(ts->src_tpm_path, tpm_util_crb_chunk_transfer,
+ "tpm-crb", NULL);
+}
+
static void tpm_crb_swtpm_migration_test(const void *data)
{
const TestState *ts = data;
@@ -54,6 +62,8 @@ int main(int argc, char **argv)
g_test_init(&argc, &argv, NULL);
qtest_add_data_func("/tpm/crb-swtpm/test", &ts, tpm_crb_swtpm_test);
+ qtest_add_data_func("/tpm/crb-chunk-swtpm/test", &ts,
+ tpm_crb_chunk_swtpm_test);
qtest_add_data_func("/tpm/crb-swtpm-migration/test", &ts,
tpm_crb_swtpm_migration_test);
ret = g_test_run();
diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
index 2cb2dd4796..744592763f 100644
--- a/tests/qtest/tpm-util.c
+++ b/tests/qtest/tpm-util.c
@@ -14,16 +14,44 @@
#include "qemu/osdep.h"
#include <glib/gstdio.h>
+#include "qemu/bswap.h"
#include "hw/acpi/tpm.h"
#include "libqtest.h"
#include "tpm-util.h"
#include "qobject/qdict.h"
+#define CRB_ADDR_START (TPM_CRB_ADDR_BASE + A_CRB_CTRL_START)
+#define CRB_ADDR_CTRL_STS (TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS)
+#define CRB_ADDR_CTRL_CMD_SIZE \
+ (TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE)
+
+#define CRB_START_INVOKE (1 << 0)
+#define CRB_START_RSP_RETRY (1 << 1)
+#define CRB_START_NEXT_CHUNK (1 << 2)
+
+void tpm_wait_till_bit_clear(QTestState *s, uint64_t addr, uint32_t mask)
+{
+ uint32_t sts;
+ uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+
+ while (true) {
+ sts = qtest_readl(s, addr);
+ if ((sts & mask) == 0) {
+ break;
+ }
+ if (g_get_monotonic_time() >= end_time) {
+ g_assert_cmphex(sts & mask, ==, 0);
+ break;
+ }
+ }
+}
+
void tpm_util_crb_transfer(QTestState *s,
const unsigned char *req, size_t req_size,
unsigned char *rsp, size_t rsp_size)
{
+ uint32_t tpm_sts;
uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
@@ -31,24 +59,76 @@ void tpm_util_crb_transfer(QTestState *s,
qtest_memwrite(s, caddr, req, req_size);
- uint32_t sts, start = 1;
- uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
- qtest_writel(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
- while (true) {
- start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
- if ((start & 1) == 0) {
- break;
+ qtest_writel(s, CRB_ADDR_START, CRB_START_INVOKE);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_INVOKE);
+
+ tpm_sts = qtest_readl(s, CRB_ADDR_CTRL_STS);
+ g_assert_cmpint(tpm_sts & 1, ==, 0);
+
+ qtest_memread(s, raddr, rsp, rsp_size);
+}
+
+void tpm_util_crb_chunk_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size)
+{
+ uint32_t tpm_sts;
+ size_t chunk_size;
+
+ uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+ uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+ uint32_t crb_ctrl_cmd_size = qtest_readl(s, CRB_ADDR_CTRL_CMD_SIZE);
+
+ chunk_size = crb_ctrl_cmd_size;
+
+ qtest_writeb(s, TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1);
+
+ for (size_t i = 0; i < req_size; i += chunk_size) {
+ bool last_chunk = false;
+ size_t current_chunk_size = chunk_size;
+
+ if (i + chunk_size > req_size) {
+ last_chunk = true;
+ current_chunk_size = req_size - i;
}
- if (g_get_monotonic_time() >= end_time) {
- break;
+
+ qtest_memwrite(s, caddr, req + i, current_chunk_size);
+
+ if (last_chunk) {
+ qtest_writel(s, CRB_ADDR_START, CRB_START_INVOKE);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_INVOKE);
+ } else {
+ qtest_writel(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
}
- };
- start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
- g_assert_cmpint(start & 1, ==, 0);
- sts = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
- g_assert_cmpint(sts & 1, ==, 0);
+ }
+ tpm_sts = qtest_readl(s, CRB_ADDR_CTRL_STS);
+ g_assert_cmpint(tpm_sts & 1, ==, 0);
- qtest_memread(s, raddr, rsp, rsp_size);
+ /*
+ * Read response in chunks
+ */
+
+ unsigned char header[10];
+ uint32_t actual_response_size = 0;
+
+ qtest_memread(s, raddr, header, sizeof(header));
+ actual_response_size = ldl_be_p(&header[2]);
+
+ if (actual_response_size > rsp_size) {
+ actual_response_size = rsp_size;
+ }
+
+ for (size_t i = 0; i < actual_response_size; i += chunk_size) {
+ size_t to_read = i + chunk_size > actual_response_size
+ ? actual_response_size - i
+ : chunk_size;
+ if (i > 0) {
+ qtest_writel(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
+ }
+ qtest_memread(s, raddr, rsp + i, to_read);
+ }
}
void tpm_util_startup(QTestState *s, tx_func *tx)
diff --git a/tests/qtest/tpm-util.h b/tests/qtest/tpm-util.h
index 0cb28dd6e5..681544e7d8 100644
--- a/tests/qtest/tpm-util.h
+++ b/tests/qtest/tpm-util.h
@@ -24,10 +24,15 @@ typedef void (tx_func)(QTestState *s,
const unsigned char *req, size_t req_size,
unsigned char *rsp, size_t rsp_size);
+void tpm_wait_till_bit_clear(QTestState *s, uint64_t addr, uint32_t mask);
void tpm_util_crb_transfer(QTestState *s,
const unsigned char *req, size_t req_size,
unsigned char *rsp, size_t rsp_size);
+void tpm_util_crb_chunk_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+
void tpm_util_startup(QTestState *s, tx_func *tx);
void tpm_util_pcrextend(QTestState *s, tx_func *tx);
void tpm_util_pcrread(QTestState *s, tx_func *tx,
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v5 05/10] test/qtest: Add test for tpm crb chunking
2026-04-22 10:30 ` [PATCH v5 05/10] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-04-29 14:26 ` Stefan Berger
0 siblings, 0 replies; 24+ messages in thread
From: Stefan Berger @ 2026-04-29 14:26 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - New test case added to the swtpm test. Data is written and read from
> the buffer in chunks.
> - The chunk size is dynamically calculated by reading the
> CRB_CTRL_CMD_SIZE address. This can be changed manually to test.
> - Add a helper function tpm_wait_till_bit_clear()
> - Note that this commit does not yet exercise the chunked read/write
> logic, as current transfer sizes remain small. Testing for large
> transfers is introduced in a subsequent patch: 'tests: Use ML-DSA-87
> operations to cause large TPM transfers with CRB'
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> tests/qtest/tpm-crb-swtpm-test.c | 10 +++
> tests/qtest/tpm-util.c | 110 ++++++++++++++++++++++++++-----
> tests/qtest/tpm-util.h | 5 ++
> 3 files changed, 110 insertions(+), 15 deletions(-)
>
> diff --git a/tests/qtest/tpm-crb-swtpm-test.c b/tests/qtest/tpm-crb-swtpm-test.c
> index ffeb1c396b..050c7b0c1f 100644
> --- a/tests/qtest/tpm-crb-swtpm-test.c
> +++ b/tests/qtest/tpm-crb-swtpm-test.c
> @@ -33,6 +33,14 @@ static void tpm_crb_swtpm_test(const void *data)
> "tpm-crb", NULL);
> }
>
> +static void tpm_crb_chunk_swtpm_test(const void *data)
> +{
> + const TestState *ts = data;
> +
> + tpm_test_swtpm_test(ts->src_tpm_path, tpm_util_crb_chunk_transfer,
> + "tpm-crb", NULL);
> +}
> +
> static void tpm_crb_swtpm_migration_test(const void *data)
> {
> const TestState *ts = data;
> @@ -54,6 +62,8 @@ int main(int argc, char **argv)
> g_test_init(&argc, &argv, NULL);
>
> qtest_add_data_func("/tpm/crb-swtpm/test", &ts, tpm_crb_swtpm_test);
> + qtest_add_data_func("/tpm/crb-chunk-swtpm/test", &ts,
> + tpm_crb_chunk_swtpm_test);
> qtest_add_data_func("/tpm/crb-swtpm-migration/test", &ts,
> tpm_crb_swtpm_migration_test);
> ret = g_test_run();
> diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
> index 2cb2dd4796..744592763f 100644
> --- a/tests/qtest/tpm-util.c
> +++ b/tests/qtest/tpm-util.c
> @@ -14,16 +14,44 @@
>
> #include "qemu/osdep.h"
> #include <glib/gstdio.h>
> +#include "qemu/bswap.h"
>
> #include "hw/acpi/tpm.h"
> #include "libqtest.h"
> #include "tpm-util.h"
> #include "qobject/qdict.h"
>
> +#define CRB_ADDR_START (TPM_CRB_ADDR_BASE + A_CRB_CTRL_START)
> +#define CRB_ADDR_CTRL_STS (TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS)
> +#define CRB_ADDR_CTRL_CMD_SIZE \
> + (TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE)
> +
> +#define CRB_START_INVOKE (1 << 0)
> +#define CRB_START_RSP_RETRY (1 << 1)
> +#define CRB_START_NEXT_CHUNK (1 << 2)
> +
> +void tpm_wait_till_bit_clear(QTestState *s, uint64_t addr, uint32_t mask)
> +{
> + uint32_t sts;
> + uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
> +
> + while (true) {
> + sts = qtest_readl(s, addr);
> + if ((sts & mask) == 0) {
> + break;
> + }
> + if (g_get_monotonic_time() >= end_time) {
> + g_assert_cmphex(sts & mask, ==, 0);
> + break;
> + }
> + }
> +}
> +
> void tpm_util_crb_transfer(QTestState *s,
> const unsigned char *req, size_t req_size,
> unsigned char *rsp, size_t rsp_size)
> {
> + uint32_t tpm_sts;
> uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
> uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
>
> @@ -31,24 +59,76 @@ void tpm_util_crb_transfer(QTestState *s,
>
> qtest_memwrite(s, caddr, req, req_size);
>
> - uint32_t sts, start = 1;
> - uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
> - qtest_writel(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
> - while (true) {
> - start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
> - if ((start & 1) == 0) {
> - break;
> + qtest_writel(s, CRB_ADDR_START, CRB_START_INVOKE);
> + tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_INVOKE);
> +
> + tpm_sts = qtest_readl(s, CRB_ADDR_CTRL_STS);
> + g_assert_cmpint(tpm_sts & 1, ==, 0);
> +
> + qtest_memread(s, raddr, rsp, rsp_size);
> +}
> +
> +void tpm_util_crb_chunk_transfer(QTestState *s,
> + const unsigned char *req, size_t req_size,
> + unsigned char *rsp, size_t rsp_size)
> +{
> + uint32_t tpm_sts;
> + size_t chunk_size;
> +
> + uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
> + uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
> + uint32_t crb_ctrl_cmd_size = qtest_readl(s, CRB_ADDR_CTRL_CMD_SIZE);
> +
> + chunk_size = crb_ctrl_cmd_size;
> +
> + qtest_writeb(s, TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1);
> +
> + for (size_t i = 0; i < req_size; i += chunk_size) {
> + bool last_chunk = false;
> + size_t current_chunk_size = chunk_size;
> +
> + if (i + chunk_size > req_size) {
> + last_chunk = true;
> + current_chunk_size = req_size - i;
> }
> - if (g_get_monotonic_time() >= end_time) {
> - break;
> +
> + qtest_memwrite(s, caddr, req + i, current_chunk_size);
> +
> + if (last_chunk) {
> + qtest_writel(s, CRB_ADDR_START, CRB_START_INVOKE);
> + tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_INVOKE);
> + } else {
> + qtest_writel(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
> + tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
> }
> - };
> - start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
> - g_assert_cmpint(start & 1, ==, 0);
> - sts = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
> - g_assert_cmpint(sts & 1, ==, 0);
> + }
> + tpm_sts = qtest_readl(s, CRB_ADDR_CTRL_STS);
> + g_assert_cmpint(tpm_sts & 1, ==, 0);
>
> - qtest_memread(s, raddr, rsp, rsp_size);
> + /*
> + * Read response in chunks
> + */
> +
> + unsigned char header[10];
> + uint32_t actual_response_size = 0;
> +
> + qtest_memread(s, raddr, header, sizeof(header));
> + actual_response_size = ldl_be_p(&header[2]);
> +
> + if (actual_response_size > rsp_size) {
> + actual_response_size = rsp_size;
> + }
> +
> + for (size_t i = 0; i < actual_response_size; i += chunk_size) {
> + size_t to_read = i + chunk_size > actual_response_size
> + ? actual_response_size - i
> + : chunk_size;
> + if (i > 0) {
> + qtest_writel(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
> + tpm_wait_till_bit_clear(s, CRB_ADDR_START, CRB_START_NEXT_CHUNK);
> + }
> + qtest_memread(s, raddr, rsp + i, to_read);
> + }
> }
>
> void tpm_util_startup(QTestState *s, tx_func *tx)
> diff --git a/tests/qtest/tpm-util.h b/tests/qtest/tpm-util.h
> index 0cb28dd6e5..681544e7d8 100644
> --- a/tests/qtest/tpm-util.h
> +++ b/tests/qtest/tpm-util.h
> @@ -24,10 +24,15 @@ typedef void (tx_func)(QTestState *s,
> const unsigned char *req, size_t req_size,
> unsigned char *rsp, size_t rsp_size);
>
> +void tpm_wait_till_bit_clear(QTestState *s, uint64_t addr, uint32_t mask);
> void tpm_util_crb_transfer(QTestState *s,
> const unsigned char *req, size_t req_size,
> unsigned char *rsp, size_t rsp_size);
>
> +void tpm_util_crb_chunk_transfer(QTestState *s,
> + const unsigned char *req, size_t req_size,
> + unsigned char *rsp, size_t rsp_size);
> +
> void tpm_util_startup(QTestState *s, tx_func *tx);
> void tpm_util_pcrextend(QTestState *s, tx_func *tx);
> void tpm_util_pcrread(QTestState *s, tx_func *tx,
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (4 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 05/10] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-29 15:36 ` Stefan Berger
` (2 more replies)
2026-04-22 10:30 ` [PATCH v5 07/10] qtests: Enable starting swtpm with a given profile Arun Menon
` (3 subsequent siblings)
9 siblings, 3 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon
From: Arun Menon <armenon@redhat.com>
- Add subsection in VMState for TPM CRB with the newly introduced
command and response buffer GByteArrays, along with a needed callback,
so that newer QEMU only sends the buffers if it is necessary.
- Implement a migration blocker to prevent migration of the VM if the
user manually enables chunking capability, cap-chunk, but the machine
type does not support it, using a new hw_compat property called
allow_chunk_migration.
- Add a post_load_errp hook so that during a migration, the buffers are
validated before destination VM is started.
Signed-off-by: Arun Menon <armenon@redhat.com>
---
hw/core/machine.c | 1 +
hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+)
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 6d27cf69a2..b590af0125 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -40,6 +40,7 @@
GlobalProperty hw_compat_11_0[] = {
{ "tpm-crb", "cap-chunk", "off"},
+ { "tpm-crb", "x-allow-chunk-migration", "off"},
};
const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 29370d6f49..23e6948aee 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -24,6 +24,7 @@
#include "hw/pci/pci_ids.h"
#include "hw/acpi/tpm.h"
#include "migration/vmstate.h"
+#include "migration/blocker.h"
#include "system/tpm_backend.h"
#include "system/tpm_util.h"
#include "system/reset.h"
@@ -51,6 +52,8 @@ struct CRBState {
TPMPPI ppi;
bool cap_chunk;
+ bool allow_chunk_migration;
+ Error *migration_blocker;
};
typedef struct CRBState CRBState;
@@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
return 0;
}
+static bool tpm_crb_chunk_needed(void *opaque)
+{
+ CRBState *s = opaque;
+
+ if (!s->allow_chunk_migration) {
+ return false;
+ }
+
+ return ((s->command_buffer && s->command_buffer->len > 0) ||
+ (s->response_buffer && s->response_buffer->len > 0));
+}
+
+static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
+{
+ CRBState *s = opaque;
+
+ if (!s->response_buffer || !s->command_buffer) {
+ error_setg(errp, "tpm-crb: Internal buffers are not allocated");
+ return false;
+ }
+ if (s->response_offset > s->response_buffer->len) {
+ error_setg(errp, "tpm-crb: Invalid response "
+ "offset %" PRIu32 " in migration stream",
+ s->response_offset);
+ return false;
+ }
+ if (s->response_buffer->len > s->be_buffer_size ||
+ s->command_buffer->len > s->be_buffer_size) {
+ error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
+ return false;
+ }
+ return true;
+}
+
+static const VMStateDescription vmstate_tpm_crb_chunk = {
+ .name = "tpm-crb/chunk",
+ .version_id = 0,
+ .needed = tpm_crb_chunk_needed,
+ .post_load_errp = tpm_crb_chunk_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
+ VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
+ VMSTATE_UINT32(response_offset, CRBState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_tpm_crb = {
.name = "tpm-crb",
.pre_save = tpm_crb_pre_save,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
VMSTATE_END_OF_LIST(),
+ },
+ .subsections = (const VMStateDescription * const []) {
+ &vmstate_tpm_crb_chunk,
+ NULL,
}
};
@@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
+ DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
+ allow_chunk_migration, true),
};
static void tpm_crb_reset(void *dev)
@@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
static void tpm_crb_realize(DeviceState *dev, Error **errp)
{
CRBState *s = CRB(dev);
+ int ret;
if (!tpm_find()) {
error_setg(errp, "at most one TPM device is permitted");
@@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
error_setg(errp, "'tpmdev' property is required");
return;
}
+ if (s->cap_chunk && !s->allow_chunk_migration) {
+ error_setg(&s->migration_blocker,
+ "The tpm-crb device does not support chunk migration with "
+ "machine version less than 11.1");
+ ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
+ if (ret < 0) {
+ return;
+ }
+ }
memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
"tpm-crb-mmio", sizeof(s->regs));
@@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
g_clear_pointer(&s->command_buffer, g_byte_array_unref);
g_clear_pointer(&s->response_buffer, g_byte_array_unref);
+
+ if (s->migration_blocker) {
+ migrate_del_blocker(&s->migration_blocker);
+ error_free(s->migration_blocker);
+ }
}
static void tpm_crb_class_init(ObjectClass *klass, const void *data)
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-22 10:30 ` [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
@ 2026-04-29 15:36 ` Stefan Berger
2026-04-29 19:49 ` Arun Menon
2026-04-29 23:14 ` Stefan Berger
2026-04-30 19:49 ` Stefan Berger
2 siblings, 1 reply; 24+ messages in thread
From: Stefan Berger @ 2026-04-29 15:36 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - Add subsection in VMState for TPM CRB with the newly introduced
> command and response buffer GByteArrays, along with a needed callback,
> so that newer QEMU only sends the buffers if it is necessary.
> - Implement a migration blocker to prevent migration of the VM if the
> user manually enables chunking capability, cap-chunk, but the machine
> type does not support it, using a new hw_compat property called
> allow_chunk_migration.
> - Add a post_load_errp hook so that during a migration, the buffers are
> validated before destination VM is started.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> ---
> hw/core/machine.c | 1 +
> hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 72 insertions(+)
>
> @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> static void tpm_crb_realize(DeviceState *dev, Error **errp)
> {
> CRBState *s = CRB(dev);
> + int ret;
>
> if (!tpm_find()) {
> error_setg(errp, "at most one TPM device is permitted");
> @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> error_setg(errp, "'tpmdev' property is required");
> return;
> }
> + if (s->cap_chunk && !s->allow_chunk_migration) {
> + error_setg(&s->migration_blocker,
> + "The tpm-crb device does not support chunk migration with "
> + "machine version less than 11.1");
> + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> + if (ret < 0) {
> + return;
Should this do an error_report() and exit(1)?
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-29 15:36 ` Stefan Berger
@ 2026-04-29 19:49 ` Arun Menon
0 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-29 19:49 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
Hi,
On Wed, Apr 29, 2026 at 11:36:39AM -0400, Stefan Berger wrote:
>
>
> On 4/22/26 6:30 AM, Arun Menon wrote:
> > From: Arun Menon <armenon@redhat.com>
> >
> > - Add subsection in VMState for TPM CRB with the newly introduced
> > command and response buffer GByteArrays, along with a needed callback,
> > so that newer QEMU only sends the buffers if it is necessary.
> > - Implement a migration blocker to prevent migration of the VM if the
> > user manually enables chunking capability, cap-chunk, but the machine
> > type does not support it, using a new hw_compat property called
> > allow_chunk_migration.
> > - Add a post_load_errp hook so that during a migration, the buffers are
> > validated before destination VM is started.
> >
> > Signed-off-by: Arun Menon <armenon@redhat.com>
> > ---
> > hw/core/machine.c | 1 +
> > hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 72 insertions(+)
> >
> > @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> > static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > {
> > CRBState *s = CRB(dev);
> > + int ret;
> > if (!tpm_find()) {
> > error_setg(errp, "at most one TPM device is permitted");
> > @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > error_setg(errp, "'tpmdev' property is required");
> > return;
> > }
> > + if (s->cap_chunk && !s->allow_chunk_migration) {
> > + error_setg(&s->migration_blocker,
> > + "The tpm-crb device does not support chunk migration with "
> > + "machine version less than 11.1");
> > + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> > + if (ret < 0) {
> > + return;
>
> Should this do an error_report() and exit(1)?
The tpm_crb_realize() function has always propagated errors in errp
back to the caller and returned in case of an error.
To be honest I followed the suit. A quick glance at the codebase shows
that the realize function in other devices also avoid calling exit().
Regards,
Arun Menon
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-22 10:30 ` [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
2026-04-29 15:36 ` Stefan Berger
@ 2026-04-29 23:14 ` Stefan Berger
2026-04-30 5:11 ` Arun Menon
2026-04-30 19:49 ` Stefan Berger
2 siblings, 1 reply; 24+ messages in thread
From: Stefan Berger @ 2026-04-29 23:14 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - Add subsection in VMState for TPM CRB with the newly introduced
> command and response buffer GByteArrays, along with a needed callback,
> so that newer QEMU only sends the buffers if it is necessary.
> - Implement a migration blocker to prevent migration of the VM if the
> user manually enables chunking capability, cap-chunk, but the machine
> type does not support it, using a new hw_compat property called
> allow_chunk_migration.
> - Add a post_load_errp hook so that during a migration, the buffers are
> validated before destination VM is started.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> ---
> hw/core/machine.c | 1 +
> hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 72 insertions(+)
>
> diff --git a/hw/core/machine.c b/hw/core/machine.c
> index 6d27cf69a2..b590af0125 100644
> --- a/hw/core/machine.c
> +++ b/hw/core/machine.c
> @@ -40,6 +40,7 @@
>
> GlobalProperty hw_compat_11_0[] = {
> { "tpm-crb", "cap-chunk", "off"},
> + { "tpm-crb", "x-allow-chunk-migration", "off"},
> };
> const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index 29370d6f49..23e6948aee 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -24,6 +24,7 @@
> #include "hw/pci/pci_ids.h"
> #include "hw/acpi/tpm.h"
> #include "migration/vmstate.h"
> +#include "migration/blocker.h"
> #include "system/tpm_backend.h"
> #include "system/tpm_util.h"
> #include "system/reset.h"
> @@ -51,6 +52,8 @@ struct CRBState {
> TPMPPI ppi;
>
> bool cap_chunk;
> + bool allow_chunk_migration;
> + Error *migration_blocker;
> };
> typedef struct CRBState CRBState;
>
> @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
> return 0;
> }
>
> +static bool tpm_crb_chunk_needed(void *opaque)
> +{
> + CRBState *s = opaque;
> +
> + if (!s->allow_chunk_migration) {
> + return false;
> + }
> +
> + return ((s->command_buffer && s->command_buffer->len > 0) ||
> + (s->response_buffer && s->response_buffer->len > 0));
> +}
> +
> +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
> +{
> + CRBState *s = opaque;
> +
> + if (!s->response_buffer || !s->command_buffer) {
> + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
Could this happen that this state type is resumed but the buffers are
not allocated?
> + return false;
> + }
> + if (s->response_offset > s->response_buffer->len) {
> + error_setg(errp, "tpm-crb: Invalid response "
> + "offset %" PRIu32 " in migration stream",
> + s->response_offset);
> + return false;
> + }
The check is correct but can this particular case occur other than
through crafted input?
> + if (s->response_buffer->len > s->be_buffer_size ||
> + s->command_buffer->len > s->be_buffer_size) {
I suppose this could happen if I was running with a PQC-enable
swtpm/libtpms and suspended at the right moment that when a chunked
command or response was in the buffer and now I am trying to resume with
a non-PQC-enabled swtpm that only has 4kb buffer, while the newer PQC
one had 8kb buffer.
> + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
> + return false;
> + }
> + return true;
> +}
> +
> +static const VMStateDescription vmstate_tpm_crb_chunk = {
> + .name = "tpm-crb/chunk",
> + .version_id = 0,
> + .needed = tpm_crb_chunk_needed,
> + .post_load_errp = tpm_crb_chunk_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
> + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
> + VMSTATE_UINT32(response_offset, CRBState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> static const VMStateDescription vmstate_tpm_crb = {
> .name = "tpm-crb",
> .pre_save = tpm_crb_pre_save,
> .fields = (const VMStateField[]) {
> VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
> VMSTATE_END_OF_LIST(),
> + },
> + .subsections = (const VMStateDescription * const []) {
> + &vmstate_tpm_crb_chunk,
> + NULL,
> }
> };
>
> @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
> DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
> DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
> DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
> + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
> + allow_chunk_migration, true),
> };
>
> static void tpm_crb_reset(void *dev)
> @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> static void tpm_crb_realize(DeviceState *dev, Error **errp)
> {
> CRBState *s = CRB(dev);
> + int ret;
>
> if (!tpm_find()) {
> error_setg(errp, "at most one TPM device is permitted");
> @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> error_setg(errp, "'tpmdev' property is required");
> return;
> }
> + if (s->cap_chunk && !s->allow_chunk_migration) {
> + error_setg(&s->migration_blocker,
> + "The tpm-crb device does not support chunk migration with "
> + "machine version less than 11.1");
> + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> + if (ret < 0) {
> + return;
> + }
> + }
>
> memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
> "tpm-crb-mmio", sizeof(s->regs));
> @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
>
> g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> +
> + if (s->migration_blocker) {
> + migrate_del_blocker(&s->migration_blocker);
> + error_free(s->migration_blocker);
> + }
> }
>
> static void tpm_crb_class_init(ObjectClass *klass, const void *data)
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-29 23:14 ` Stefan Berger
@ 2026-04-30 5:11 ` Arun Menon
2026-04-30 11:46 ` Stefan Berger
0 siblings, 1 reply; 24+ messages in thread
From: Arun Menon @ 2026-04-30 5:11 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On Wed, Apr 29, 2026 at 07:14:38PM -0400, Stefan Berger wrote:
>
>
> On 4/22/26 6:30 AM, Arun Menon wrote:
> > From: Arun Menon <armenon@redhat.com>
> >
> > - Add subsection in VMState for TPM CRB with the newly introduced
> > command and response buffer GByteArrays, along with a needed callback,
> > so that newer QEMU only sends the buffers if it is necessary.
> > - Implement a migration blocker to prevent migration of the VM if the
> > user manually enables chunking capability, cap-chunk, but the machine
> > type does not support it, using a new hw_compat property called
> > allow_chunk_migration.
> > - Add a post_load_errp hook so that during a migration, the buffers are
> > validated before destination VM is started.
> >
> > Signed-off-by: Arun Menon <armenon@redhat.com>
> > ---
> > hw/core/machine.c | 1 +
> > hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 72 insertions(+)
> >
> > diff --git a/hw/core/machine.c b/hw/core/machine.c
> > index 6d27cf69a2..b590af0125 100644
> > --- a/hw/core/machine.c
> > +++ b/hw/core/machine.c
> > @@ -40,6 +40,7 @@
> >
> > GlobalProperty hw_compat_11_0[] = {
> > { "tpm-crb", "cap-chunk", "off"},
> > + { "tpm-crb", "x-allow-chunk-migration", "off"},
> > };
> > const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
> >
> > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> > index 29370d6f49..23e6948aee 100644
> > --- a/hw/tpm/tpm_crb.c
> > +++ b/hw/tpm/tpm_crb.c
> > @@ -24,6 +24,7 @@
> > #include "hw/pci/pci_ids.h"
> > #include "hw/acpi/tpm.h"
> > #include "migration/vmstate.h"
> > +#include "migration/blocker.h"
> > #include "system/tpm_backend.h"
> > #include "system/tpm_util.h"
> > #include "system/reset.h"
> > @@ -51,6 +52,8 @@ struct CRBState {
> > TPMPPI ppi;
> >
> > bool cap_chunk;
> > + bool allow_chunk_migration;
> > + Error *migration_blocker;
> > };
> > typedef struct CRBState CRBState;
> >
> > @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
> > return 0;
> > }
> >
> > +static bool tpm_crb_chunk_needed(void *opaque)
> > +{
> > + CRBState *s = opaque;
> > +
> > + if (!s->allow_chunk_migration) {
> > + return false;
> > + }
> > +
> > + return ((s->command_buffer && s->command_buffer->len > 0) ||
> > + (s->response_buffer && s->response_buffer->len > 0));
> > +}
> > +
> > +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
> > +{
> > + CRBState *s = opaque;
> > +
> > + if (!s->response_buffer || !s->command_buffer) {
> > + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
>
> Could this happen that this state type is resumed but the buffers are not
> allocated?
Yes, this is not required. Its just a defense. The tpm_realize()
function should allocate the command and response buffers.
>
> > + return false;
> > + }
> > + if (s->response_offset > s->response_buffer->len) {
> > + error_setg(errp, "tpm-crb: Invalid response "
> > + "offset %" PRIu32 " in migration stream",
> > + s->response_offset);
> > + return false;
> > + }
>
> The check is correct but can this particular case occur other than through
> crafted input?
Not that I can think of. The check is indeed added to address malicious
migration stream / crafted input.
>
> > + if (s->response_buffer->len > s->be_buffer_size ||
> > + s->command_buffer->len > s->be_buffer_size) {
>
> I suppose this could happen if I was running with a PQC-enable swtpm/libtpms
> and suspended at the right moment that when a chunked command or response
> was in the buffer and now I am trying to resume with a non-PQC-enabled swtpm
> that only has 4kb buffer, while the newer PQC one had 8kb buffer.
>
Yes. This check is for safety. We do not want to migrate to a VM that
has a tpm backend that does not support big buffers.
> > + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
> > + return false;
> > + }
> > + return true;
> > +}
> > +
> > +static const VMStateDescription vmstate_tpm_crb_chunk = {
> > + .name = "tpm-crb/chunk",
> > + .version_id = 0,
> > + .needed = tpm_crb_chunk_needed,
> > + .post_load_errp = tpm_crb_chunk_post_load,
> > + .fields = (const VMStateField[]) {
> > + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
> > + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
> > + VMSTATE_UINT32(response_offset, CRBState),
> > + VMSTATE_END_OF_LIST()
> > + }
> > +};
> > +
> > static const VMStateDescription vmstate_tpm_crb = {
> > .name = "tpm-crb",
> > .pre_save = tpm_crb_pre_save,
> > .fields = (const VMStateField[]) {
> > VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
> > VMSTATE_END_OF_LIST(),
> > + },
> > + .subsections = (const VMStateDescription * const []) {
> > + &vmstate_tpm_crb_chunk,
> > + NULL,
> > }
> > };
> >
> > @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
> > DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
> > DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
> > DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
> > + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
> > + allow_chunk_migration, true),
> > };
> >
> > static void tpm_crb_reset(void *dev)
> > @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> > static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > {
> > CRBState *s = CRB(dev);
> > + int ret;
> >
> > if (!tpm_find()) {
> > error_setg(errp, "at most one TPM device is permitted");
> > @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > error_setg(errp, "'tpmdev' property is required");
> > return;
> > }
> > + if (s->cap_chunk && !s->allow_chunk_migration) {
> > + error_setg(&s->migration_blocker,
> > + "The tpm-crb device does not support chunk migration with "
> > + "machine version less than 11.1");
> > + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> > + if (ret < 0) {
> > + return;
> > + }
> > + }
> >
> > memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
> > "tpm-crb-mmio", sizeof(s->regs));
> > @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
> >
> > g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> > g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> > +
> > + if (s->migration_blocker) {
> > + migrate_del_blocker(&s->migration_blocker);
> > + error_free(s->migration_blocker);
> > + }
> > }
> >
> > static void tpm_crb_class_init(ObjectClass *klass, const void *data)
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-30 5:11 ` Arun Menon
@ 2026-04-30 11:46 ` Stefan Berger
2026-05-04 7:12 ` Arun Menon
0 siblings, 1 reply; 24+ messages in thread
From: Stefan Berger @ 2026-04-30 11:46 UTC (permalink / raw)
To: armenon
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On 4/30/26 1:11 AM, Arun Menon wrote:
> On Wed, Apr 29, 2026 at 07:14:38PM -0400, Stefan Berger wrote:
>>
>>
>> On 4/22/26 6:30 AM, Arun Menon wrote:
>>> From: Arun Menon <armenon@redhat.com>
>>>
>>> - Add subsection in VMState for TPM CRB with the newly introduced
>>> command and response buffer GByteArrays, along with a needed callback,
>>> so that newer QEMU only sends the buffers if it is necessary.
>>> - Implement a migration blocker to prevent migration of the VM if the
>>> user manually enables chunking capability, cap-chunk, but the machine
>>> type does not support it, using a new hw_compat property called
>>> allow_chunk_migration.
>>> - Add a post_load_errp hook so that during a migration, the buffers are
>>> validated before destination VM is started.
>>>
>>> Signed-off-by: Arun Menon <armenon@redhat.com>
>>> ---
>>> hw/core/machine.c | 1 +
>>> hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 72 insertions(+)
>>>
>>> diff --git a/hw/core/machine.c b/hw/core/machine.c
>>> index 6d27cf69a2..b590af0125 100644
>>> --- a/hw/core/machine.c
>>> +++ b/hw/core/machine.c
>>> @@ -40,6 +40,7 @@
>>>
>>> GlobalProperty hw_compat_11_0[] = {
>>> { "tpm-crb", "cap-chunk", "off"},
>>> + { "tpm-crb", "x-allow-chunk-migration", "off"},
>>> };
>>> const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
>>>
>>> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
>>> index 29370d6f49..23e6948aee 100644
>>> --- a/hw/tpm/tpm_crb.c
>>> +++ b/hw/tpm/tpm_crb.c
>>> @@ -24,6 +24,7 @@
>>> #include "hw/pci/pci_ids.h"
>>> #include "hw/acpi/tpm.h"
>>> #include "migration/vmstate.h"
>>> +#include "migration/blocker.h"
>>> #include "system/tpm_backend.h"
>>> #include "system/tpm_util.h"
>>> #include "system/reset.h"
>>> @@ -51,6 +52,8 @@ struct CRBState {
>>> TPMPPI ppi;
>>>
>>> bool cap_chunk;
>>> + bool allow_chunk_migration;
>>> + Error *migration_blocker;
>>> };
>>> typedef struct CRBState CRBState;
>>>
>>> @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
>>> return 0;
>>> }
>>>
>>> +static bool tpm_crb_chunk_needed(void *opaque)
>>> +{
>>> + CRBState *s = opaque;
>>> +
>>> + if (!s->allow_chunk_migration) {
>>> + return false;
>>> + }
>>> +
>>> + return ((s->command_buffer && s->command_buffer->len > 0) ||
>>> + (s->response_buffer && s->response_buffer->len > 0));
>>> +}
>>> +
>>> +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
>>> +{
>>> + CRBState *s = opaque;
>>> +
>>> + if (!s->response_buffer || !s->command_buffer) {
>>> + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
>>
>> Could this happen that this state type is resumed but the buffers are not
>> allocated?
>
> Yes, this is not required. Its just a defense. The tpm_realize()
> function should allocate the command and response buffers.
>
>>
>>> + return false;
>>> + }
>>> + if (s->response_offset > s->response_buffer->len) {
>>> + error_setg(errp, "tpm-crb: Invalid response "
>>> + "offset %" PRIu32 " in migration stream",
>>> + s->response_offset);
>>> + return false;
>>> + }
>>
>> The check is correct but can this particular case occur other than through
>> crafted input?
>
> Not that I can think of. The check is indeed added to address malicious
> migration stream / crafted input.
I think that devices are assuming trusted input for all devices resumed
from their previously stored state...
>
>>
>>> + if (s->response_buffer->len > s->be_buffer_size ||
>>> + s->command_buffer->len > s->be_buffer_size) {
>>
>> I suppose this could happen if I was running with a PQC-enable swtpm/libtpms
>> and suspended at the right moment that when a chunked command or response
>> was in the buffer and now I am trying to resume with a non-PQC-enabled swtpm
>> that only has 4kb buffer, while the newer PQC one had 8kb buffer.
>>
>
> Yes. This check is for safety. We do not want to migrate to a VM that
> has a tpm backend that does not support big buffers.
>
>>> + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
>>> + return false;
>>> + }
>>> + return true;
>>> +}
>>> +
>>> +static const VMStateDescription vmstate_tpm_crb_chunk = {
>>> + .name = "tpm-crb/chunk",
>>> + .version_id = 0,
>>> + .needed = tpm_crb_chunk_needed,
>>> + .post_load_errp = tpm_crb_chunk_post_load,
>>> + .fields = (const VMStateField[]) {
>>> + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
>>> + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
>>> + VMSTATE_UINT32(response_offset, CRBState),
>>> + VMSTATE_END_OF_LIST()
>>> + }
>>> +};
>>> +
>>> static const VMStateDescription vmstate_tpm_crb = {
>>> .name = "tpm-crb",
>>> .pre_save = tpm_crb_pre_save,
>>> .fields = (const VMStateField[]) {
>>> VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
>>> VMSTATE_END_OF_LIST(),
>>> + },
>>> + .subsections = (const VMStateDescription * const []) {
>>> + &vmstate_tpm_crb_chunk,
>>> + NULL,
>>> }
>>> };
>>>
>>> @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
>>> DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
>>> DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
>>> DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
>>> + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
>>> + allow_chunk_migration, true),
>>> };
>>>
>>> static void tpm_crb_reset(void *dev)
>>> @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
>>> static void tpm_crb_realize(DeviceState *dev, Error **errp)
>>> {
>>> CRBState *s = CRB(dev);
>>> + int ret;
>>>
>>> if (!tpm_find()) {
>>> error_setg(errp, "at most one TPM device is permitted");
>>> @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
>>> error_setg(errp, "'tpmdev' property is required");
>>> return;
>>> }
>>> + if (s->cap_chunk && !s->allow_chunk_migration) {
>>> + error_setg(&s->migration_blocker,
>>> + "The tpm-crb device does not support chunk migration with "
>>> + "machine version less than 11.1");
>>> + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
>>> + if (ret < 0) {
>>> + return;
>>> + }
>>> + }
>>>
>>> memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
>>> "tpm-crb-mmio", sizeof(s->regs));
>>> @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
>>>
>>> g_clear_pointer(&s->command_buffer, g_byte_array_unref);
>>> g_clear_pointer(&s->response_buffer, g_byte_array_unref);
>>> +
>>> + if (s->migration_blocker) {
>>> + migrate_del_blocker(&s->migration_blocker);
>>> + error_free(s->migration_blocker);
>>> + }
>>> }
>>>
>>> static void tpm_crb_class_init(ObjectClass *klass, const void *data)
>>
>
> Regards,
> Arun Menon
>
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-30 11:46 ` Stefan Berger
@ 2026-05-04 7:12 ` Arun Menon
2026-05-04 14:33 ` Stefan Berger
0 siblings, 1 reply; 24+ messages in thread
From: Arun Menon @ 2026-05-04 7:12 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On Thu, Apr 30, 2026 at 07:46:33AM -0400, Stefan Berger wrote:
>
>
> On 4/30/26 1:11 AM, Arun Menon wrote:
> > On Wed, Apr 29, 2026 at 07:14:38PM -0400, Stefan Berger wrote:
> > >
> > >
> > > On 4/22/26 6:30 AM, Arun Menon wrote:
> > > > From: Arun Menon <armenon@redhat.com>
> > > >
> > > > - Add subsection in VMState for TPM CRB with the newly introduced
> > > > command and response buffer GByteArrays, along with a needed callback,
> > > > so that newer QEMU only sends the buffers if it is necessary.
> > > > - Implement a migration blocker to prevent migration of the VM if the
> > > > user manually enables chunking capability, cap-chunk, but the machine
> > > > type does not support it, using a new hw_compat property called
> > > > allow_chunk_migration.
> > > > - Add a post_load_errp hook so that during a migration, the buffers are
> > > > validated before destination VM is started.
> > > >
> > > > Signed-off-by: Arun Menon <armenon@redhat.com>
> > > > ---
> > > > hw/core/machine.c | 1 +
> > > > hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> > > > 2 files changed, 72 insertions(+)
> > > >
> > > > diff --git a/hw/core/machine.c b/hw/core/machine.c
> > > > index 6d27cf69a2..b590af0125 100644
> > > > --- a/hw/core/machine.c
> > > > +++ b/hw/core/machine.c
> > > > @@ -40,6 +40,7 @@
> > > >
> > > > GlobalProperty hw_compat_11_0[] = {
> > > > { "tpm-crb", "cap-chunk", "off"},
> > > > + { "tpm-crb", "x-allow-chunk-migration", "off"},
> > > > };
> > > > const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
> > > >
> > > > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> > > > index 29370d6f49..23e6948aee 100644
> > > > --- a/hw/tpm/tpm_crb.c
> > > > +++ b/hw/tpm/tpm_crb.c
> > > > @@ -24,6 +24,7 @@
> > > > #include "hw/pci/pci_ids.h"
> > > > #include "hw/acpi/tpm.h"
> > > > #include "migration/vmstate.h"
> > > > +#include "migration/blocker.h"
> > > > #include "system/tpm_backend.h"
> > > > #include "system/tpm_util.h"
> > > > #include "system/reset.h"
> > > > @@ -51,6 +52,8 @@ struct CRBState {
> > > > TPMPPI ppi;
> > > >
> > > > bool cap_chunk;
> > > > + bool allow_chunk_migration;
> > > > + Error *migration_blocker;
> > > > };
> > > > typedef struct CRBState CRBState;
> > > >
> > > > @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
> > > > return 0;
> > > > }
> > > >
> > > > +static bool tpm_crb_chunk_needed(void *opaque)
> > > > +{
> > > > + CRBState *s = opaque;
> > > > +
> > > > + if (!s->allow_chunk_migration) {
> > > > + return false;
> > > > + }
> > > > +
> > > > + return ((s->command_buffer && s->command_buffer->len > 0) ||
> > > > + (s->response_buffer && s->response_buffer->len > 0));
> > > > +}
> > > > +
> > > > +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
> > > > +{
> > > > + CRBState *s = opaque;
> > > > +
> > > > + if (!s->response_buffer || !s->command_buffer) {
> > > > + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
> > >
> > > Could this happen that this state type is resumed but the buffers are not
> > > allocated?
> >
> > Yes, this is not required. Its just a defense. The tpm_realize()
> > function should allocate the command and response buffers.
> >
> > >
> > > > + return false;
> > > > + }
> > > > + if (s->response_offset > s->response_buffer->len) {
> > > > + error_setg(errp, "tpm-crb: Invalid response "
> > > > + "offset %" PRIu32 " in migration stream",
> > > > + s->response_offset);
> > > > + return false;
> > > > + }
> > >
> > > The check is correct but can this particular case occur other than through
> > > crafted input?
> >
> > Not that I can think of. The check is indeed added to address malicious
> > migration stream / crafted input.
>
> I think that devices are assuming trusted input for all devices resumed from
> their previously stored state...
I see. Shall I remove these two checks at the beginning and keep only
the last one?
>
> >
> > >
> > > > + if (s->response_buffer->len > s->be_buffer_size ||
> > > > + s->command_buffer->len > s->be_buffer_size) {
> > >
> > > I suppose this could happen if I was running with a PQC-enable swtpm/libtpms
> > > and suspended at the right moment that when a chunked command or response
> > > was in the buffer and now I am trying to resume with a non-PQC-enabled swtpm
> > > that only has 4kb buffer, while the newer PQC one had 8kb buffer.
> > >
> >
> > Yes. This check is for safety. We do not want to migrate to a VM that
> > has a tpm backend that does not support big buffers.
> >
> > > > + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
> > > > + return false;
> > > > + }
> > > > + return true;
> > > > +}
> > > > +
> > > > +static const VMStateDescription vmstate_tpm_crb_chunk = {
> > > > + .name = "tpm-crb/chunk",
> > > > + .version_id = 0,
> > > > + .needed = tpm_crb_chunk_needed,
> > > > + .post_load_errp = tpm_crb_chunk_post_load,
> > > > + .fields = (const VMStateField[]) {
> > > > + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
> > > > + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
> > > > + VMSTATE_UINT32(response_offset, CRBState),
> > > > + VMSTATE_END_OF_LIST()
> > > > + }
> > > > +};
> > > > +
> > > > static const VMStateDescription vmstate_tpm_crb = {
> > > > .name = "tpm-crb",
> > > > .pre_save = tpm_crb_pre_save,
> > > > .fields = (const VMStateField[]) {
> > > > VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
> > > > VMSTATE_END_OF_LIST(),
> > > > + },
> > > > + .subsections = (const VMStateDescription * const []) {
> > > > + &vmstate_tpm_crb_chunk,
> > > > + NULL,
> > > > }
> > > > };
> > > >
> > > > @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
> > > > DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
> > > > DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
> > > > DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
> > > > + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
> > > > + allow_chunk_migration, true),
> > > > };
> > > >
> > > > static void tpm_crb_reset(void *dev)
> > > > @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> > > > static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > > > {
> > > > CRBState *s = CRB(dev);
> > > > + int ret;
> > > >
> > > > if (!tpm_find()) {
> > > > error_setg(errp, "at most one TPM device is permitted");
> > > > @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > > > error_setg(errp, "'tpmdev' property is required");
> > > > return;
> > > > }
> > > > + if (s->cap_chunk && !s->allow_chunk_migration) {
> > > > + error_setg(&s->migration_blocker,
> > > > + "The tpm-crb device does not support chunk migration with "
> > > > + "machine version less than 11.1");
> > > > + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> > > > + if (ret < 0) {
> > > > + return;
> > > > + }
> > > > + }
> > > >
> > > > memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
> > > > "tpm-crb-mmio", sizeof(s->regs));
> > > > @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
> > > >
> > > > g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> > > > g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> > > > +
> > > > + if (s->migration_blocker) {
> > > > + migrate_del_blocker(&s->migration_blocker);
> > > > + error_free(s->migration_blocker);
> > > > + }
> > > > }
> > > >
> > > > static void tpm_crb_class_init(ObjectClass *klass, const void *data)
> > >
> >
> > Regards,
> > Arun Menon
> >
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-05-04 7:12 ` Arun Menon
@ 2026-05-04 14:33 ` Stefan Berger
2026-05-04 17:42 ` Arun Menon
0 siblings, 1 reply; 24+ messages in thread
From: Stefan Berger @ 2026-05-04 14:33 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On 5/4/26 3:12 AM, Arun Menon wrote:
> On Thu, Apr 30, 2026 at 07:46:33AM -0400, Stefan Berger wrote:
>>
>>
>> On 4/30/26 1:11 AM, Arun Menon wrote:
>>> On Wed, Apr 29, 2026 at 07:14:38PM -0400, Stefan Berger wrote:
>>>>
>>>>
>>>> On 4/22/26 6:30 AM, Arun Menon wrote:
>>>>> From: Arun Menon <armenon@redhat.com>
>>>>>
>>>>> - Add subsection in VMState for TPM CRB with the newly introduced
>>>>> command and response buffer GByteArrays, along with a needed callback,
>>>>> so that newer QEMU only sends the buffers if it is necessary.
>>>>> - Implement a migration blocker to prevent migration of the VM if the
>>>>> user manually enables chunking capability, cap-chunk, but the machine
>>>>> type does not support it, using a new hw_compat property called
>>>>> allow_chunk_migration.
>>>>> - Add a post_load_errp hook so that during a migration, the buffers are
>>>>> validated before destination VM is started.
>>>>>
>>>>> Signed-off-by: Arun Menon <armenon@redhat.com>
>>>>> ---
>>>>> hw/core/machine.c | 1 +
>>>>> hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
>>>>> 2 files changed, 72 insertions(+)
>>>>>
>>>>> diff --git a/hw/core/machine.c b/hw/core/machine.c
>>>>> index 6d27cf69a2..b590af0125 100644
>>>>> --- a/hw/core/machine.c
>>>>> +++ b/hw/core/machine.c
>>>>> @@ -40,6 +40,7 @@
>>>>>
>>>>> GlobalProperty hw_compat_11_0[] = {
>>>>> { "tpm-crb", "cap-chunk", "off"},
>>>>> + { "tpm-crb", "x-allow-chunk-migration", "off"},
>>>>> };
>>>>> const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
>>>>>
>>>>> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
>>>>> index 29370d6f49..23e6948aee 100644
>>>>> --- a/hw/tpm/tpm_crb.c
>>>>> +++ b/hw/tpm/tpm_crb.c
>>>>> @@ -24,6 +24,7 @@
>>>>> #include "hw/pci/pci_ids.h"
>>>>> #include "hw/acpi/tpm.h"
>>>>> #include "migration/vmstate.h"
>>>>> +#include "migration/blocker.h"
>>>>> #include "system/tpm_backend.h"
>>>>> #include "system/tpm_util.h"
>>>>> #include "system/reset.h"
>>>>> @@ -51,6 +52,8 @@ struct CRBState {
>>>>> TPMPPI ppi;
>>>>>
>>>>> bool cap_chunk;
>>>>> + bool allow_chunk_migration;
>>>>> + Error *migration_blocker;
>>>>> };
>>>>> typedef struct CRBState CRBState;
>>>>>
>>>>> @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
>>>>> return 0;
>>>>> }
>>>>>
>>>>> +static bool tpm_crb_chunk_needed(void *opaque)
>>>>> +{
>>>>> + CRBState *s = opaque;
>>>>> +
>>>>> + if (!s->allow_chunk_migration) {
>>>>> + return false;
>>>>> + }
>>>>> +
>>>>> + return ((s->command_buffer && s->command_buffer->len > 0) ||
>>>>> + (s->response_buffer && s->response_buffer->len > 0));
>>>>> +}
>>>>> +
>>>>> +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
>>>>> +{
>>>>> + CRBState *s = opaque;
>>>>> +
>>>>> + if (!s->response_buffer || !s->command_buffer) {
>>>>> + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
>>>>
>>>> Could this happen that this state type is resumed but the buffers are not
>>>> allocated?
>>>
>>> Yes, this is not required. Its just a defense. The tpm_realize()
>>> function should allocate the command and response buffers.
>>>
>>>>
>>>>> + return false;
>>>>> + }
>>>>> + if (s->response_offset > s->response_buffer->len) {
>>>>> + error_setg(errp, "tpm-crb: Invalid response "
>>>>> + "offset %" PRIu32 " in migration stream",
>>>>> + s->response_offset);
>>>>> + return false;
>>>>> + }
>>>>
>>>> The check is correct but can this particular case occur other than through
>>>> crafted input?
>>>
>>> Not that I can think of. The check is indeed added to address malicious
>>> migration stream / crafted input.
>>
>> I think that devices are assuming trusted input for all devices resumed from
>> their previously stored state...
>
> I see. Shall I remove these two checks at the beginning and keep only
> the last one?
I think so. For the last one I think you should write a description what
role the external emulator can play here.
>
>>
>>>
>>>>
>>>>> + if (s->response_buffer->len > s->be_buffer_size ||
>>>>> + s->command_buffer->len > s->be_buffer_size) {
>>>>
>>>> I suppose this could happen if I was running with a PQC-enable swtpm/libtpms
>>>> and suspended at the right moment that when a chunked command or response
>>>> was in the buffer and now I am trying to resume with a non-PQC-enabled swtpm
>>>> that only has 4kb buffer, while the newer PQC one had 8kb buffer.
>>>>
>>>
>>> Yes. This check is for safety. We do not want to migrate to a VM that
>>> has a tpm backend that does not support big buffers.
>>>
>>>>> + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
>>>>> + return false;
>>>>> + }
>>>>> + return true;
>>>>> +}
>>>>> +
>>>>> +static const VMStateDescription vmstate_tpm_crb_chunk = {
>>>>> + .name = "tpm-crb/chunk",
>>>>> + .version_id = 0,
>>>>> + .needed = tpm_crb_chunk_needed,
>>>>> + .post_load_errp = tpm_crb_chunk_post_load,
>>>>> + .fields = (const VMStateField[]) {
>>>>> + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
>>>>> + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
>>>>> + VMSTATE_UINT32(response_offset, CRBState),
>>>>> + VMSTATE_END_OF_LIST()
>>>>> + }
>>>>> +};
>>>>> +
>>>>> static const VMStateDescription vmstate_tpm_crb = {
>>>>> .name = "tpm-crb",
>>>>> .pre_save = tpm_crb_pre_save,
>>>>> .fields = (const VMStateField[]) {
>>>>> VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
>>>>> VMSTATE_END_OF_LIST(),
>>>>> + },
>>>>> + .subsections = (const VMStateDescription * const []) {
>>>>> + &vmstate_tpm_crb_chunk,
>>>>> + NULL,
>>>>> }
>>>>> };
>>>>>
>>>>> @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
>>>>> DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
>>>>> DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
>>>>> DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
>>>>> + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
>>>>> + allow_chunk_migration, true),
>>>>> };
>>>>>
>>>>> static void tpm_crb_reset(void *dev)
>>>>> @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
>>>>> static void tpm_crb_realize(DeviceState *dev, Error **errp)
>>>>> {
>>>>> CRBState *s = CRB(dev);
>>>>> + int ret;
>>>>>
>>>>> if (!tpm_find()) {
>>>>> error_setg(errp, "at most one TPM device is permitted");
>>>>> @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
>>>>> error_setg(errp, "'tpmdev' property is required");
>>>>> return;
>>>>> }
>>>>> + if (s->cap_chunk && !s->allow_chunk_migration) {
>>>>> + error_setg(&s->migration_blocker,
>>>>> + "The tpm-crb device does not support chunk migration with "
>>>>> + "machine version less than 11.1");
>>>>> + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
>>>>> + if (ret < 0) {
>>>>> + return;
>>>>> + }
>>>>> + }
>>>>>
>>>>> memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
>>>>> "tpm-crb-mmio", sizeof(s->regs));
>>>>> @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
>>>>>
>>>>> g_clear_pointer(&s->command_buffer, g_byte_array_unref);
>>>>> g_clear_pointer(&s->response_buffer, g_byte_array_unref);
>>>>> +
>>>>> + if (s->migration_blocker) {
>>>>> + migrate_del_blocker(&s->migration_blocker);
>>>>> + error_free(s->migration_blocker);
>>>>> + }
>>>>> }
>>>>>
>>>>> static void tpm_crb_class_init(ObjectClass *klass, const void *data)
>>>>
>>>
>>> Regards,
>>> Arun Menon
>>>
>>
>
> Regards,
> Arun Menon
>
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-05-04 14:33 ` Stefan Berger
@ 2026-05-04 17:42 ` Arun Menon
0 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-05-04 17:42 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Zhao Liu, Stefan Berger, Marcel Apfelbaum,
Laurent Vivier, Paolo Bonzini, Fabiano Rosas, Igor Mammedov,
marcandre.lureau, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On Mon, May 04, 2026 at 10:33:38AM -0400, Stefan Berger wrote:
>
>
> On 5/4/26 3:12 AM, Arun Menon wrote:
> > On Thu, Apr 30, 2026 at 07:46:33AM -0400, Stefan Berger wrote:
> > >
> > >
> > > On 4/30/26 1:11 AM, Arun Menon wrote:
> > > > On Wed, Apr 29, 2026 at 07:14:38PM -0400, Stefan Berger wrote:
> > > > >
> > > > >
> > > > > On 4/22/26 6:30 AM, Arun Menon wrote:
> > > > > > From: Arun Menon <armenon@redhat.com>
> > > > > >
> > > > > > - Add subsection in VMState for TPM CRB with the newly introduced
> > > > > > command and response buffer GByteArrays, along with a needed callback,
> > > > > > so that newer QEMU only sends the buffers if it is necessary.
> > > > > > - Implement a migration blocker to prevent migration of the VM if the
> > > > > > user manually enables chunking capability, cap-chunk, but the machine
> > > > > > type does not support it, using a new hw_compat property called
> > > > > > allow_chunk_migration.
> > > > > > - Add a post_load_errp hook so that during a migration, the buffers are
> > > > > > validated before destination VM is started.
> > > > > >
> > > > > > Signed-off-by: Arun Menon <armenon@redhat.com>
> > > > > > ---
> > > > > > hw/core/machine.c | 1 +
> > > > > > hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> > > > > > 2 files changed, 72 insertions(+)
> > > > > >
> > > > > > diff --git a/hw/core/machine.c b/hw/core/machine.c
> > > > > > index 6d27cf69a2..b590af0125 100644
> > > > > > --- a/hw/core/machine.c
> > > > > > +++ b/hw/core/machine.c
> > > > > > @@ -40,6 +40,7 @@
> > > > > >
> > > > > > GlobalProperty hw_compat_11_0[] = {
> > > > > > { "tpm-crb", "cap-chunk", "off"},
> > > > > > + { "tpm-crb", "x-allow-chunk-migration", "off"},
> > > > > > };
> > > > > > const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
> > > > > >
> > > > > > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> > > > > > index 29370d6f49..23e6948aee 100644
> > > > > > --- a/hw/tpm/tpm_crb.c
> > > > > > +++ b/hw/tpm/tpm_crb.c
> > > > > > @@ -24,6 +24,7 @@
> > > > > > #include "hw/pci/pci_ids.h"
> > > > > > #include "hw/acpi/tpm.h"
> > > > > > #include "migration/vmstate.h"
> > > > > > +#include "migration/blocker.h"
> > > > > > #include "system/tpm_backend.h"
> > > > > > #include "system/tpm_util.h"
> > > > > > #include "system/reset.h"
> > > > > > @@ -51,6 +52,8 @@ struct CRBState {
> > > > > > TPMPPI ppi;
> > > > > >
> > > > > > bool cap_chunk;
> > > > > > + bool allow_chunk_migration;
> > > > > > + Error *migration_blocker;
> > > > > > };
> > > > > > typedef struct CRBState CRBState;
> > > > > >
> > > > > > @@ -353,12 +356,63 @@ static int tpm_crb_pre_save(void *opaque)
> > > > > > return 0;
> > > > > > }
> > > > > >
> > > > > > +static bool tpm_crb_chunk_needed(void *opaque)
> > > > > > +{
> > > > > > + CRBState *s = opaque;
> > > > > > +
> > > > > > + if (!s->allow_chunk_migration) {
> > > > > > + return false;
> > > > > > + }
> > > > > > +
> > > > > > + return ((s->command_buffer && s->command_buffer->len > 0) ||
> > > > > > + (s->response_buffer && s->response_buffer->len > 0));
> > > > > > +}
> > > > > > +
> > > > > > +static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
> > > > > > +{
> > > > > > + CRBState *s = opaque;
> > > > > > +
> > > > > > + if (!s->response_buffer || !s->command_buffer) {
> > > > > > + error_setg(errp, "tpm-crb: Internal buffers are not allocated");
> > > > >
> > > > > Could this happen that this state type is resumed but the buffers are not
> > > > > allocated?
> > > >
> > > > Yes, this is not required. Its just a defense. The tpm_realize()
> > > > function should allocate the command and response buffers.
> > > >
> > > > >
> > > > > > + return false;
> > > > > > + }
> > > > > > + if (s->response_offset > s->response_buffer->len) {
> > > > > > + error_setg(errp, "tpm-crb: Invalid response "
> > > > > > + "offset %" PRIu32 " in migration stream",
> > > > > > + s->response_offset);
> > > > > > + return false;
> > > > > > + }
> > > > >
> > > > > The check is correct but can this particular case occur other than through
> > > > > crafted input?
> > > >
> > > > Not that I can think of. The check is indeed added to address malicious
> > > > migration stream / crafted input.
> > >
> > > I think that devices are assuming trusted input for all devices resumed from
> > > their previously stored state...
> >
> > I see. Shall I remove these two checks at the beginning and keep only
> > the last one?
>
> I think so. For the last one I think you should write a description what
> role the external emulator can play here.
Sure. I have updated it in v6. Thank you.
>
>
> >
> > >
> > > >
> > > > >
> > > > > > + if (s->response_buffer->len > s->be_buffer_size ||
> > > > > > + s->command_buffer->len > s->be_buffer_size) {
> > > > >
> > > > > I suppose this could happen if I was running with a PQC-enable swtpm/libtpms
> > > > > and suspended at the right moment that when a chunked command or response
> > > > > was in the buffer and now I am trying to resume with a non-PQC-enabled swtpm
> > > > > that only has 4kb buffer, while the newer PQC one had 8kb buffer.
> > > > >
> > > >
> > > > Yes. This check is for safety. We do not want to migrate to a VM that
> > > > has a tpm backend that does not support big buffers.
> > > >
> > > > > > + error_setg(errp, "tpm-crb: Buffer sizes exceed backend capacity");
> > > > > > + return false;
> > > > > > + }
> > > > > > + return true;
> > > > > > +}
> > > > > > +
> > > > > > +static const VMStateDescription vmstate_tpm_crb_chunk = {
> > > > > > + .name = "tpm-crb/chunk",
> > > > > > + .version_id = 0,
> > > > > > + .needed = tpm_crb_chunk_needed,
> > > > > > + .post_load_errp = tpm_crb_chunk_post_load,
> > > > > > + .fields = (const VMStateField[]) {
> > > > > > + VMSTATE_GBYTEARRAY(command_buffer, CRBState, 0),
> > > > > > + VMSTATE_GBYTEARRAY(response_buffer, CRBState, 0),
> > > > > > + VMSTATE_UINT32(response_offset, CRBState),
> > > > > > + VMSTATE_END_OF_LIST()
> > > > > > + }
> > > > > > +};
> > > > > > +
> > > > > > static const VMStateDescription vmstate_tpm_crb = {
> > > > > > .name = "tpm-crb",
> > > > > > .pre_save = tpm_crb_pre_save,
> > > > > > .fields = (const VMStateField[]) {
> > > > > > VMSTATE_UINT32_ARRAY(regs, CRBState, TPM_CRB_R_MAX),
> > > > > > VMSTATE_END_OF_LIST(),
> > > > > > + },
> > > > > > + .subsections = (const VMStateDescription * const []) {
> > > > > > + &vmstate_tpm_crb_chunk,
> > > > > > + NULL,
> > > > > > }
> > > > > > };
> > > > > >
> > > > > > @@ -366,6 +420,8 @@ static const Property tpm_crb_properties[] = {
> > > > > > DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
> > > > > > DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
> > > > > > DEFINE_PROP_BOOL("cap-chunk", CRBState, cap_chunk, true),
> > > > > > + DEFINE_PROP_BOOL("x-allow-chunk-migration", CRBState,
> > > > > > + allow_chunk_migration, true),
> > > > > > };
> > > > > >
> > > > > > static void tpm_crb_reset(void *dev)
> > > > > > @@ -422,6 +478,7 @@ static void tpm_crb_reset(void *dev)
> > > > > > static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > > > > > {
> > > > > > CRBState *s = CRB(dev);
> > > > > > + int ret;
> > > > > >
> > > > > > if (!tpm_find()) {
> > > > > > error_setg(errp, "at most one TPM device is permitted");
> > > > > > @@ -431,6 +488,15 @@ static void tpm_crb_realize(DeviceState *dev, Error **errp)
> > > > > > error_setg(errp, "'tpmdev' property is required");
> > > > > > return;
> > > > > > }
> > > > > > + if (s->cap_chunk && !s->allow_chunk_migration) {
> > > > > > + error_setg(&s->migration_blocker,
> > > > > > + "The tpm-crb device does not support chunk migration with "
> > > > > > + "machine version less than 11.1");
> > > > > > + ret = migrate_add_blocker_normal(&s->migration_blocker, errp);
> > > > > > + if (ret < 0) {
> > > > > > + return;
> > > > > > + }
> > > > > > + }
> > > > > >
> > > > > > memory_region_init_io(&s->mmio, OBJECT(s), &tpm_crb_memory_ops, s,
> > > > > > "tpm-crb-mmio", sizeof(s->regs));
> > > > > > @@ -463,6 +529,11 @@ static void tpm_crb_unrealize(DeviceState *dev)
> > > > > >
> > > > > > g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> > > > > > g_clear_pointer(&s->response_buffer, g_byte_array_unref);
> > > > > > +
> > > > > > + if (s->migration_blocker) {
> > > > > > + migrate_del_blocker(&s->migration_blocker);
> > > > > > + error_free(s->migration_blocker);
> > > > > > + }
> > > > > > }
> > > > > >
> > > > > > static void tpm_crb_class_init(ObjectClass *klass, const void *data)
> > > > >
> > > >
> > > > Regards,
> > > > Arun Menon
> > > >
> > >
> >
> > Regards,
> > Arun Menon
> >
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-22 10:30 ` [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
2026-04-29 15:36 ` Stefan Berger
2026-04-29 23:14 ` Stefan Berger
@ 2026-04-30 19:49 ` Stefan Berger
2026-05-04 17:43 ` Arun Menon
2 siblings, 1 reply; 24+ messages in thread
From: Stefan Berger @ 2026-04-30 19:49 UTC (permalink / raw)
To: Arun Menon, qemu-devel, Marc-André Lureau
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha
On 4/22/26 6:30 AM, Arun Menon wrote:
> From: Arun Menon <armenon@redhat.com>
>
> - Add subsection in VMState for TPM CRB with the newly introduced
> command and response buffer GByteArrays, along with a needed callback,
> so that newer QEMU only sends the buffers if it is necessary.
> - Implement a migration blocker to prevent migration of the VM if the
> user manually enables chunking capability, cap-chunk, but the machine
> type does not support it, using a new hw_compat property called
> allow_chunk_migration.
> - Add a post_load_errp hook so that during a migration, the buffers are
> validated before destination VM is started.
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> ---
> hw/core/machine.c | 1 +
> hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 72 insertions(+)
>
> diff --git a/hw/core/machine.c b/hw/core/machine.c
> index 6d27cf69a2..b590af0125 100644
> --- a/hw/core/machine.c
> +++ b/hw/core/machine.c
> @@ -40,6 +40,7 @@
>
> GlobalProperty hw_compat_11_0[] = {
> { "tpm-crb", "cap-chunk", "off"},
> + { "tpm-crb", "x-allow-chunk-migration", "off"},
Nit: A space after the "off". Also in previous patch that added "cap-chunk".
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-04-30 19:49 ` Stefan Berger
@ 2026-05-04 17:43 ` Arun Menon
0 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-05-04 17:43 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Marc-André Lureau, Zhao Liu, Stefan Berger,
Marcel Apfelbaum, Laurent Vivier, Paolo Bonzini, Fabiano Rosas,
Igor Mammedov, Philippe Mathieu-Daudé, Michael S. Tsirkin,
Yanan Wang, Ani Sinha
On Thu, Apr 30, 2026 at 03:49:47PM -0400, Stefan Berger wrote:
>
>
> On 4/22/26 6:30 AM, Arun Menon wrote:
> > From: Arun Menon <armenon@redhat.com>
> >
> > - Add subsection in VMState for TPM CRB with the newly introduced
> > command and response buffer GByteArrays, along with a needed callback,
> > so that newer QEMU only sends the buffers if it is necessary.
> > - Implement a migration blocker to prevent migration of the VM if the
> > user manually enables chunking capability, cap-chunk, but the machine
> > type does not support it, using a new hw_compat property called
> > allow_chunk_migration.
> > - Add a post_load_errp hook so that during a migration, the buffers are
> > validated before destination VM is started.
> >
> > Signed-off-by: Arun Menon <armenon@redhat.com>
> > ---
> > hw/core/machine.c | 1 +
> > hw/tpm/tpm_crb.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 72 insertions(+)
> >
> > diff --git a/hw/core/machine.c b/hw/core/machine.c
> > index 6d27cf69a2..b590af0125 100644
> > --- a/hw/core/machine.c
> > +++ b/hw/core/machine.c
> > @@ -40,6 +40,7 @@
> > GlobalProperty hw_compat_11_0[] = {
> > { "tpm-crb", "cap-chunk", "off"},
> > + { "tpm-crb", "x-allow-chunk-migration", "off"},
>
> Nit: A space after the "off". Also in previous patch that added "cap-chunk".
Thanks. Changed it in v6.
>
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v5 07/10] qtests: Enable starting swtpm with a given profile
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (5 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 06/10] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 08/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with CRB Arun Menon
` (2 subsequent siblings)
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Stefan Berger <stefanb@linux.ibm.com>
Enable swtpm to start with a given profile by passing it to swtpm on the
command line using
--profile name=<profile name>
Remove any existing TPM 2 state file since applying a new profile to
existing state would be refused by swtpm.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Arun Menon <armenon@redhat.com>
---
tests/qtest/tpm-tests.c | 6 +++---
tests/qtest/tpm-util.c | 9 ++++++++-
tests/qtest/tpm-util.h | 3 ++-
3 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/tests/qtest/tpm-tests.c b/tests/qtest/tpm-tests.c
index 197714f8d9..f71d882990 100644
--- a/tests/qtest/tpm-tests.c
+++ b/tests/qtest/tpm-tests.c
@@ -43,7 +43,7 @@ void tpm_test_swtpm_test(const char *src_tpm_path, tx_func *tx,
return;
}
- succ = tpm_util_swtpm_start(src_tpm_path, &swtpm_pid, &addr, &error);
+ succ = tpm_util_swtpm_start(src_tpm_path, &swtpm_pid, &addr, NULL, &error);
g_assert_true(succ);
args = g_strdup_printf(
@@ -91,11 +91,11 @@ void tpm_test_swtpm_migration_test(const char *src_tpm_path,
}
succ = tpm_util_swtpm_start(src_tpm_path, &src_tpm_pid,
- &src_tpm_addr, &error);
+ &src_tpm_addr, NULL, &error);
g_assert_true(succ);
succ = tpm_util_swtpm_start(dst_tpm_path, &dst_tpm_pid,
- &dst_tpm_addr, &error);
+ &dst_tpm_addr, NULL, &error);
g_assert_true(succ);
tpm_util_migration_start_qemu(&src_qemu, &dst_qemu,
diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
index 744592763f..cbba5b1c1f 100644
--- a/tests/qtest/tpm-util.c
+++ b/tests/qtest/tpm-util.c
@@ -212,7 +212,8 @@ bool tpm_util_swtpm_has_tpm2(void)
}
gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
- SocketAddress **addr, GError **error)
+ SocketAddress **addr, const char *profilename,
+ GError **error)
{
char *swtpm_argv_tpmstate = g_strdup_printf("dir=%s", path);
char *swtpm_argv_ctrl = g_strdup_printf("type=unixio,path=%s/sock",
@@ -222,11 +223,17 @@ gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
g_strdup("--tpmstate"), swtpm_argv_tpmstate,
g_strdup("--ctrl"), swtpm_argv_ctrl,
g_strdup("--tpm2"),
+ profilename ? g_strdup("--profile") : NULL,
+ profilename ? g_strdup_printf("name=%s", profilename) : NULL,
NULL
};
+ g_autofree char *swtpm_state_file;
gboolean succ;
unsigned i;
+ swtpm_state_file = g_strdup_printf("%s/tpm2-00.permall", path);
+ g_unlink(swtpm_state_file);
+
*addr = g_new0(SocketAddress, 1);
(*addr)->type = SOCKET_ADDRESS_TYPE_UNIX;
(*addr)->u.q_unix.path = g_build_filename(path, "sock", NULL);
diff --git a/tests/qtest/tpm-util.h b/tests/qtest/tpm-util.h
index 681544e7d8..ca2d7d173f 100644
--- a/tests/qtest/tpm-util.h
+++ b/tests/qtest/tpm-util.h
@@ -41,7 +41,8 @@ void tpm_util_pcrread(QTestState *s, tx_func *tx,
bool tpm_util_swtpm_has_tpm2(void);
gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
- SocketAddress **addr, GError **error);
+ SocketAddress **addr, const char *profilename,
+ GError **error);
void tpm_util_swtpm_kill(GPid pid);
void tpm_util_migrate(QTestState *who, const char *uri);
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 08/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with CRB
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (6 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 07/10] qtests: Enable starting swtpm with a given profile Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 09/10] tpm: Extend TPM TIS buffer size to 8192 bytes Arun Menon
2026-04-22 10:30 ` [PATCH v5 10/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with TIS Arun Menon
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Stefan Berger <stefanb@linux.ibm.com>
To test large data transfers (receiving and sending) that make use of a CRB
chunked transfer, create an ML-DSA-87 key and sign some data with it and
receive the 4627 bytes signature. After this send the signature back to the
TPM to have the TPM verify the signature.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Arun Menon <armenon@redhat.com>
---
tests/qtest/tpm-crb-swtpm-test.c | 5 +-
tests/qtest/tpm-tests.c | 96 ++++++++++++++++++++++++++++++++
tests/qtest/tpm-tests.h | 4 ++
tests/qtest/tpm-util.c | 37 ++++++++++++
tests/qtest/tpm-util.h | 2 +
5 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/tests/qtest/tpm-crb-swtpm-test.c b/tests/qtest/tpm-crb-swtpm-test.c
index 050c7b0c1f..541fd58133 100644
--- a/tests/qtest/tpm-crb-swtpm-test.c
+++ b/tests/qtest/tpm-crb-swtpm-test.c
@@ -37,8 +37,9 @@ static void tpm_crb_chunk_swtpm_test(const void *data)
{
const TestState *ts = data;
- tpm_test_swtpm_test(ts->src_tpm_path, tpm_util_crb_chunk_transfer,
- "tpm-crb", NULL);
+ tpm_test_swtpm_large_tx_test(ts->src_tpm_path,
+ tpm_util_crb_chunk_transfer,
+ "tpm-crb", NULL);
}
static void tpm_crb_swtpm_migration_test(const void *data)
diff --git a/tests/qtest/tpm-tests.c b/tests/qtest/tpm-tests.c
index f71d882990..21811f3a2e 100644
--- a/tests/qtest/tpm-tests.c
+++ b/tests/qtest/tpm-tests.c
@@ -13,6 +13,7 @@
*/
#include "qemu/osdep.h"
+#include "system/tpm_util.h"
#include <glib/gstdio.h>
#include "libqtest-single.h"
@@ -130,3 +131,98 @@ void tpm_test_swtpm_migration_test(const char *src_tpm_path,
g_unlink(src_tpm_addr->u.q_unix.path);
qapi_free_SocketAddress(src_tpm_addr);
}
+
+void tpm_test_swtpm_large_tx_test(const char *src_tpm_path, tx_func *tx,
+ const char *ifmodel,
+ const char *machine_options)
+{
+ unsigned char signature[2 + 2 + 4627]; /* TPMT_SIGNATURE */
+ unsigned char response[8192];
+ unsigned char request[8192];
+ SocketAddress *addr = NULL;
+ GError *error = NULL;
+ char *args = NULL;
+ GPid swtpm_pid;
+ QTestState *s;
+ gboolean succ;
+
+ if (tpm_test_swtpm_skip()) {
+ return;
+ }
+
+ /* Large transfers based on ML-DSA operations required default-v2 profile */
+ if (!tpm_util_swtpm_has_profile("default-v2", "ml-dsa")) {
+ return;
+ }
+
+ succ = tpm_util_swtpm_start(src_tpm_path, &swtpm_pid, &addr, "default-v2",
+ &error);
+ g_assert_true(succ);
+
+ args = g_strdup_printf(
+ "%s "
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device %s,tpmdev=dev",
+ machine_options ? : "", addr->u.q_unix.path, ifmodel);
+
+ s = qtest_start(args);
+ g_free(args);
+
+ tpm_util_startup(s, tx);
+
+ static const unsigned char tpm_createprimary_mldsa[] =
+ "\x80\x02\x00\x00\x00\x38\x00\x00\x01\x31\x40\x00\x00\x07\x00\x00"
+ "\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00"
+ "\x00\x00\x0f\x00\xa1\x00\x0b\x00\x04\x04\x72\x00\x00\x00\x03\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00";
+ tx(s, tpm_createprimary_mldsa, sizeof(tpm_createprimary_mldsa),
+ response, sizeof(response));
+ g_assert_cmpint(tpm_cmd_get_errcode(response), ==, 0);
+ g_assert_cmpint(tpm_cmd_get_size(response), ==, 2831);
+
+ static const unsigned char tpm_signsequencestart[] =
+ "\x80\x01\x00\x00\x00\x12\x00\x00\x01\xaa\x80\x00\x00\x00\x00\x00"
+ "\x00\x00";
+ tx(s, tpm_signsequencestart, sizeof(tpm_signsequencestart),
+ response, sizeof(response));
+ g_assert_cmpint(tpm_cmd_get_errcode(response), ==, 0);
+ g_assert_cmpint(tpm_cmd_get_size(response), ==, 14);
+
+ /* Complete sequence and get signature */
+ static const unsigned char tpm_signsequencecomplete[] =
+ "\x80\x02\x00\x00\x00\x2a\x00\x00\x01\xa4\x80\x00\x00\x01\x80\x00"
+ "\x00\x00\x00\x00\x00\x12\x40\x00\x00\x09\x00\x00\x00\x00\x00\x40"
+ "\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00";
+ tx(s, tpm_signsequencecomplete, sizeof(tpm_signsequencecomplete),
+ response, sizeof(response));
+ g_assert_cmpint(tpm_cmd_get_errcode(response), ==, 0);
+ g_assert_cmpint(tpm_cmd_get_size(response), ==, 4655);
+
+ /* TPMT_SIGNATURE found at offset 14 */
+ memcpy(signature, &response[14], sizeof(signature));
+
+ static const unsigned char tpm_verifysequencestart[] =
+ "\x80\x01\x00\x00\x00\x14\x00\x00\x01\xa9\x80\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00";
+ tx(s, tpm_verifysequencestart, sizeof(tpm_verifysequencestart),
+ response, sizeof(response));
+ g_assert_cmpint(tpm_cmd_get_errcode(response), ==, 0);
+ g_assert_cmpint(tpm_cmd_get_size(response), ==, 14);
+
+ /* TPM2_VerifySequenceComplete */
+ memcpy(request,
+ "\x80\x02\x00\x00\x12\x36\x00\x00\x01\xa3\x80\x00\x00\x01\x80\x00"
+ "\x00\x00\x00\x00\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00",
+ 31);
+ memcpy(&request[31], signature, sizeof(signature));
+ tx(s, request, 31 + sizeof(signature), response, sizeof(response));
+ g_assert_cmpint(tpm_cmd_get_errcode(response), ==, 0);
+ g_assert_cmpint(tpm_cmd_get_size(response), ==, 27);
+
+ qtest_end();
+ tpm_util_swtpm_kill(swtpm_pid);
+
+ g_unlink(addr->u.q_unix.path);
+ qapi_free_SocketAddress(addr);
+}
diff --git a/tests/qtest/tpm-tests.h b/tests/qtest/tpm-tests.h
index 07ba60d26e..6993ce40dc 100644
--- a/tests/qtest/tpm-tests.h
+++ b/tests/qtest/tpm-tests.h
@@ -24,4 +24,8 @@ void tpm_test_swtpm_migration_test(const char *src_tpm_path,
const char *ifmodel,
const char *machine_options);
+void tpm_test_swtpm_large_tx_test(const char *src_tpm_path, tx_func *tx,
+ const char *ifmodel,
+ const char *machine_options);
+
#endif /* TESTS_TPM_TESTS_H */
diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
index cbba5b1c1f..ad4325aade 100644
--- a/tests/qtest/tpm-util.c
+++ b/tests/qtest/tpm-util.c
@@ -211,6 +211,43 @@ bool tpm_util_swtpm_has_tpm2(void)
return has_tpm2;
}
+bool tpm_util_swtpm_has_profile(const char *profilename,
+ const char *content)
+{
+ bool has_profile = false;
+ char *out = NULL;
+ static const char *argv[] = {
+ "swtpm", "socket", "--tpm2", "--print-profiles", NULL
+ };
+
+ if (!tpm_util_swtpm_has_tpm2()) {
+ return false;
+ }
+
+ if (!g_spawn_sync(NULL /* working_dir */,
+ (char **)argv,
+ NULL /* envp */,
+ G_SPAWN_SEARCH_PATH,
+ NULL /* child_setup */,
+ NULL /* user_data */,
+ &out,
+ NULL /* err */,
+ NULL /* exit_status */,
+ NULL)) {
+ return false;
+ }
+
+ if (strstr(out, profilename)) {
+ has_profile = true;
+ }
+ if (has_profile && content && strstr(out, content) == NULL) {
+ has_profile = false;
+ }
+
+ g_free(out);
+ return has_profile;
+}
+
gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
SocketAddress **addr, const char *profilename,
GError **error)
diff --git a/tests/qtest/tpm-util.h b/tests/qtest/tpm-util.h
index ca2d7d173f..90790f30db 100644
--- a/tests/qtest/tpm-util.h
+++ b/tests/qtest/tpm-util.h
@@ -39,6 +39,8 @@ void tpm_util_pcrread(QTestState *s, tx_func *tx,
const unsigned char *exp_resp, size_t exp_resp_size);
bool tpm_util_swtpm_has_tpm2(void);
+bool tpm_util_swtpm_has_profile(const char *profilename,
+ const char *content);
gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
SocketAddress **addr, const char *profilename,
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 09/10] tpm: Extend TPM TIS buffer size to 8192 bytes
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (7 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 08/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with CRB Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
2026-04-22 10:30 ` [PATCH v5 10/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with TIS Arun Menon
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Stefan Berger <stefanb@linux.ibm.com>
Extend the TIS buffer size to 8192 bytes and store the first 4096 bytes
using VMSTATE_PARTIAL_BUFFER and the rest using VMSTATE_BUFFER_START_MIDDLE
when necessary. It is necessary to store the buffer beyond original 4096
bytes when:
- the user has written more than 4096 bytes to the buffer
- the TPM 2 response is larger than 4096 bytes
Use the .needed function of the VMStateDescription interface to check
whether the bytes in the buffer beyond 4096 bytes needed to be saved.
The .pre_save function is called before the .needed function so that we
can be sure to have received any response packet from the TPM.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
[ Arun Menon - Removed WIP tag and TODO for submission ]
Signed-off-by: Arun Menon <armenon@redhat.com>
---
hw/tpm/tpm_tis.h | 2 ++
hw/tpm/tpm_tis_common.c | 23 +++++++++++++++++++++++
hw/tpm/tpm_tis_i2c.c | 24 +++++++++++++++++++++++-
hw/tpm/tpm_tis_isa.c | 24 +++++++++++++++++++++++-
hw/tpm/tpm_tis_sysbus.c | 24 +++++++++++++++++++++++-
5 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
index 184632ff66..d35c332287 100644
--- a/hw/tpm/tpm_tis.h
+++ b/hw/tpm/tpm_tis.h
@@ -90,4 +90,6 @@ uint32_t tpm_tis_read_data(TPMState *s, hwaddr addr, unsigned size);
void tpm_tis_write_data(TPMState *s, hwaddr addr, uint64_t val, uint32_t size);
uint16_t tpm_tis_get_checksum(TPMState *s);
+bool tpm_tis_ext_buffer_migration_needed(struct TPMState *s);
+
#endif /* TPM_TPM_TIS_H */
diff --git a/hw/tpm/tpm_tis_common.c b/hw/tpm/tpm_tis_common.c
index f594b15b8a..dffb0a411e 100644
--- a/hw/tpm/tpm_tis_common.c
+++ b/hw/tpm/tpm_tis_common.c
@@ -890,3 +890,26 @@ const VMStateDescription vmstate_locty = {
}
};
+bool tpm_tis_ext_buffer_migration_needed(struct TPMState *s)
+{
+ if (!TPM_TIS_IS_VALID_LOCTY(s->active_locty)) {
+ return false;
+ }
+
+ switch (s->loc[s->active_locty].state) {
+ case TPM_TIS_STATE_IDLE:
+ case TPM_TIS_STATE_READY:
+ return false;
+ case TPM_TIS_STATE_RECEPTION:
+ return s->rw_offset >= 4096;
+ case TPM_TIS_STATE_EXECUTION:
+ /*
+ * TPM is executing: we cannot know the size of TPM response.
+ * .pre_save must have been called before (should never get here).
+ */
+ return false;
+ case TPM_TIS_STATE_COMPLETION:
+ return (tpm_cmd_get_size(&s->buffer) >= 4096);
+ }
+ return false;
+}
diff --git a/hw/tpm/tpm_tis_i2c.c b/hw/tpm/tpm_tis_i2c.c
index 9f13e0ec12..a01df7e5a0 100644
--- a/hw/tpm/tpm_tis_i2c.c
+++ b/hw/tpm/tpm_tis_i2c.c
@@ -110,13 +110,31 @@ static int tpm_tis_i2c_post_load(void *opaque, int version_id)
return 0;
}
+static bool tpm_tis_ext_buffer_migration_needed_i2c(void *opaque)
+{
+ TPMStateI2C *i2cst = opaque;
+
+ return tpm_tis_ext_buffer_migration_needed(&i2cst->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_ext_buffer_i2c = {
+ .name = "tpm-tis/ext_buffer",
+ .version_id = 0,
+ .needed = tpm_tis_ext_buffer_migration_needed_i2c,
+ .pre_save = tpm_tis_i2c_pre_save,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BUFFER_START_MIDDLE(state.buffer, TPMStateI2C, 4096),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_tpm_tis_i2c = {
.name = "tpm-tis-i2c",
.version_id = 0,
.pre_save = tpm_tis_i2c_pre_save,
.post_load = tpm_tis_i2c_post_load,
.fields = (const VMStateField[]) {
- VMSTATE_BUFFER(state.buffer, TPMStateI2C),
+ VMSTATE_PARTIAL_BUFFER(state.buffer, TPMStateI2C, 4096),
VMSTATE_UINT16(state.rw_offset, TPMStateI2C),
VMSTATE_UINT8(state.active_locty, TPMStateI2C),
VMSTATE_UINT8(state.aborting_locty, TPMStateI2C),
@@ -133,6 +151,10 @@ static const VMStateDescription vmstate_tpm_tis_i2c = {
VMSTATE_UINT8(csum_enable, TPMStateI2C),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription *const[]) {
+ &vmstate_tpm_tis_ext_buffer_i2c,
+ NULL,
}
};
diff --git a/hw/tpm/tpm_tis_isa.c b/hw/tpm/tpm_tis_isa.c
index 61e95434f5..73038f1a10 100644
--- a/hw/tpm/tpm_tis_isa.c
+++ b/hw/tpm/tpm_tis_isa.c
@@ -49,12 +49,30 @@ static int tpm_tis_pre_save_isa(void *opaque)
return tpm_tis_pre_save(&isadev->state);
}
+static bool tpm_tis_ext_buffer_migration_needed_isa(void *opaque)
+{
+ TPMStateISA *isadev = opaque;
+
+ return tpm_tis_ext_buffer_migration_needed(&isadev->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_ext_buffer_isa = {
+ .name = "tpm-tis/ext_buffer",
+ .version_id = 0,
+ .needed = tpm_tis_ext_buffer_migration_needed_isa,
+ .pre_save = tpm_tis_pre_save_isa,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BUFFER_START_MIDDLE(state.buffer, TPMStateISA, 4096),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_tpm_tis_isa = {
.name = "tpm-tis",
.version_id = 0,
.pre_save = tpm_tis_pre_save_isa,
.fields = (const VMStateField[]) {
- VMSTATE_BUFFER(state.buffer, TPMStateISA),
+ VMSTATE_PARTIAL_BUFFER(state.buffer, TPMStateISA, 4096),
VMSTATE_UINT16(state.rw_offset, TPMStateISA),
VMSTATE_UINT8(state.active_locty, TPMStateISA),
VMSTATE_UINT8(state.aborting_locty, TPMStateISA),
@@ -64,6 +82,10 @@ static const VMStateDescription vmstate_tpm_tis_isa = {
vmstate_locty, TPMLocality),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription *const[]) {
+ &vmstate_tpm_tis_ext_buffer_isa,
+ NULL,
}
};
diff --git a/hw/tpm/tpm_tis_sysbus.c b/hw/tpm/tpm_tis_sysbus.c
index e9372e7316..86fc5a592c 100644
--- a/hw/tpm/tpm_tis_sysbus.c
+++ b/hw/tpm/tpm_tis_sysbus.c
@@ -48,12 +48,30 @@ static int tpm_tis_pre_save_sysbus(void *opaque)
return tpm_tis_pre_save(&sbdev->state);
}
+static bool tpm_tis_ext_buffer_migration_needed_sysbus(void *opaque)
+{
+ TPMStateSysBus *sbdev = opaque;
+
+ return tpm_tis_ext_buffer_migration_needed(&sbdev->state);
+}
+
+static const VMStateDescription vmstate_tpm_tis_ext_buffer_sysbus = {
+ .name = "tpm-tis/ext_buffer",
+ .version_id = 0,
+ .needed = tpm_tis_ext_buffer_migration_needed_sysbus,
+ .pre_save = tpm_tis_pre_save_sysbus,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BUFFER_START_MIDDLE(state.buffer, TPMStateSysBus, 4096),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_tpm_tis_sysbus = {
.name = "tpm-tis",
.version_id = 0,
.pre_save = tpm_tis_pre_save_sysbus,
.fields = (const VMStateField[]) {
- VMSTATE_BUFFER(state.buffer, TPMStateSysBus),
+ VMSTATE_PARTIAL_BUFFER(state.buffer, TPMStateSysBus, 4096),
VMSTATE_UINT16(state.rw_offset, TPMStateSysBus),
VMSTATE_UINT8(state.active_locty, TPMStateSysBus),
VMSTATE_UINT8(state.aborting_locty, TPMStateSysBus),
@@ -63,6 +81,10 @@ static const VMStateDescription vmstate_tpm_tis_sysbus = {
0, vmstate_locty, TPMLocality),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription *const[]) {
+ &vmstate_tpm_tis_ext_buffer_sysbus,
+ NULL,
}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v5 10/10] tests: Use ML-DSA-87 operations to caused large TPM transfers with TIS
2026-04-22 10:30 [PATCH v5 00/10] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (8 preceding siblings ...)
2026-04-22 10:30 ` [PATCH v5 09/10] tpm: Extend TPM TIS buffer size to 8192 bytes Arun Menon
@ 2026-04-22 10:30 ` Arun Menon
9 siblings, 0 replies; 24+ messages in thread
From: Arun Menon @ 2026-04-22 10:30 UTC (permalink / raw)
To: qemu-devel
Cc: Zhao Liu, Stefan Berger, Marcel Apfelbaum, Laurent Vivier,
Paolo Bonzini, Fabiano Rosas, Igor Mammedov, marcandre.lureau,
Philippe Mathieu-Daudé, Michael S. Tsirkin, Yanan Wang,
Ani Sinha, Arun Menon, Stefan Berger
From: Stefan Berger <stefanb@linux.ibm.com>
Test a large data transfer with the TIS. To do this, first create an
ML-DSA-87 key and sign some data with it. Then receive the 4627 bytes
signature and then send the signature back to have the TPM verify it.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Signed-off-by: Arun Menon <armenon@redhat.com>
---
tests/qtest/tpm-tis-swtpm-test.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/tests/qtest/tpm-tis-swtpm-test.c b/tests/qtest/tpm-tis-swtpm-test.c
index 105e42e21d..56d2a340c9 100644
--- a/tests/qtest/tpm-tis-swtpm-test.c
+++ b/tests/qtest/tpm-tis-swtpm-test.c
@@ -36,6 +36,15 @@ static void tpm_tis_swtpm_test(const void *data)
"tpm-tis", NULL);
}
+static void tpm_tis_large_tx_swtpm_test(const void *data)
+{
+ const TestState *ts = data;
+
+ tpm_test_swtpm_large_tx_test(ts->src_tpm_path,
+ tpm_tis_transfer,
+ "tpm-tis", NULL);
+}
+
static void tpm_tis_swtpm_migration_test(const void *data)
{
const TestState *ts = data;
@@ -57,6 +66,8 @@ int main(int argc, char **argv)
g_test_init(&argc, &argv, NULL);
qtest_add_data_func("/tpm/tis-swtpm/test", &ts, tpm_tis_swtpm_test);
+ qtest_add_data_func("/tpm/tis-larget-tx-swtpm/test", &ts,
+ tpm_tis_large_tx_swtpm_test);
qtest_add_data_func("/tpm/tis-swtpm-migration/test", &ts,
tpm_tis_swtpm_migration_test);
ret = g_test_run();
--
2.53.0
^ permalink raw reply related [flat|nested] 24+ messages in thread