* [RFC v2 1/7] hw/tpm: Add TPM CRB chunking fields
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-19 13:53 ` [RFC v2 2/7] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
` (5 subsequent siblings)
6 siblings, 0 replies; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon,
Stefan Berger
- 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]
[1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
---
hw/tpm/tpm_crb.c | 3 +++
include/hw/acpi/tpm.h | 5 ++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 8723536f93..0a1c7ecdc6 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -59,6 +59,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)
@@ -262,6 +263,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, CRB_INTF_CAP_CRB_CHUNK);
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..03d452d2b5 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, invoke, 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] 21+ messages in thread* [RFC v2 2/7] hw/tpm: Refactor CRB_CTRL_START register access
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-03-19 13:53 ` [RFC v2 1/7] hw/tpm: Add TPM CRB chunking fields Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-19 13:53 ` [RFC v2 3/7] hw/tpm: Add internal buffer state for chunking Arun Menon
` (4 subsequent siblings)
6 siblings, 0 replies; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon,
Stefan Berger
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>
---
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 0a1c7ecdc6..bc55908786 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -145,7 +145,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, invoke, 1);
s->cmd = (TPMBackendCmd) {
.in = mem,
.in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size),
@@ -194,7 +194,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, invoke, 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] 21+ messages in thread* [RFC v2 3/7] hw/tpm: Add internal buffer state for chunking
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-03-19 13:53 ` [RFC v2 1/7] hw/tpm: Add TPM CRB chunking fields Arun Menon
2026-03-19 13:53 ` [RFC v2 2/7] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-26 11:27 ` marcandre.lureau
2026-03-19 13:53 ` [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic Arun Menon
` (3 subsequent siblings)
6 siblings, 1 reply; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon
- 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>
---
hw/tpm/tpm_crb.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index bc55908786..5ea1a4a970 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;
+ size_t response_offset;
bool ppi_enabled;
TPMPPI ppi;
@@ -85,6 +88,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)
{
@@ -134,9 +144,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:
@@ -240,6 +252,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));
@@ -306,6 +319,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));
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [RFC v2 3/7] hw/tpm: Add internal buffer state for chunking
2026-03-19 13:53 ` [RFC v2 3/7] hw/tpm: Add internal buffer state for chunking Arun Menon
@ 2026-03-26 11:27 ` marcandre.lureau
0 siblings, 0 replies; 21+ messages in thread
From: marcandre.lureau @ 2026-03-26 11:27 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On Thu, 19 Mar 2026 19:23:12 +0530, Arun Menon <armenon@redhat.com> wrote:
Hi
> - Introduce GByteArray buffers to hold the command request and response
> data during chunked TPM CRB transactions.
> - Add helper function to clean them.
>
I would introduce them where they are actually used, but ok
>
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index bc559087867..5ea1a4a9706 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -306,6 +319,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();
These buffers are allocated here but never freed. Please add an
unrealize or finalize handler that calls g_byte_array_unref() on
both buffers.
--
Marc-André Lureau <marcandre.lureau@redhat.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (2 preceding siblings ...)
2026-03-19 13:53 ` [RFC v2 3/7] hw/tpm: Add internal buffer state for chunking Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-26 11:27 ` marcandre.lureau
2026-03-19 13:53 ` [RFC v2 5/7] test/qtest: Add test for tpm crb chunking Arun Menon
` (2 subsequent siblings)
6 siblings, 1 reply; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon,
Stefan Berger
- 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_rc1_121225.pdf
Signed-off-by: Arun Menon <armenon@redhat.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
---
hw/tpm/tpm_crb.c | 148 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 132 insertions(+), 16 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 5ea1a4a970..e61c04aee0 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"
@@ -65,6 +66,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),
@@ -80,6 +82,8 @@ enum crb_ctrl_req {
enum crb_start {
CRB_START_INVOKE = BIT(0),
+ CRB_START_RESP_RETRY = BIT(1),
+ CRB_START_NEXT_CHUNK = BIT(2),
};
enum crb_cancel {
@@ -122,6 +126,68 @@ 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, invoke, 0);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
+ tpm_crb_clear_internal_buffers(s);
+ error_report("Command size '%d' less than TPM header size '%d'",
+ total_request_size, 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)
{
@@ -152,20 +218,48 @@ 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, invoke, 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 (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, invoke, 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) {
+ /*
+ * 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_RESP_RETRY) {
+ if (s->response_buffer->len > 0) {
+ 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:
@@ -205,13 +299,36 @@ static const MemoryRegionOps tpm_crb_memory_ops = {
static void tpm_crb_request_completed(TPMIf *ti, int ret)
{
CRBState *s = CRB(ti);
+ void *mem = memory_region_get_ram_ptr(&s->cmdmem);
ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
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;
+
+ /*
+ * Send the first chunk. Subsequent chunks will be sent using
+ * tpm_crb_fill_command_response()
+ */
+ uint32_t to_copy = MIN(CRB_CTRL_CMD_SIZE, s->response_buffer->len);
+ memcpy(mem, s->response_buffer->data, 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);
+ ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
+ 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)
@@ -288,8 +405,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] 21+ messages in thread* Re: [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic
2026-03-19 13:53 ` [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic Arun Menon
@ 2026-03-26 11:27 ` marcandre.lureau
2026-03-31 17:07 ` Arun Menon
0 siblings, 1 reply; 21+ messages in thread
From: marcandre.lureau @ 2026-03-26 11:27 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Stefan Berger
On Thu, 19 Mar 2026 19:23:13 +0530, Arun Menon <armenon@redhat.com> wrote:
Hi,
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index 5ea1a4a9706..e61c04aee0b 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -80,6 +82,8 @@ enum crb_ctrl_req {
>
> enum crb_start {
> CRB_START_INVOKE = BIT(0),
> + CRB_START_RESP_RETRY = BIT(1),
Hmm, maybe we should rename that field, since it is "Start" in the spec (it changed?).
> + CRB_START_NEXT_CHUNK = BIT(2),
> };
And follow the spec naming here "RspRetry" -> RSP_RETRY
> @@ -122,6 +126,68 @@ static uint8_t tpm_crb_get_active_locty(CRBState *s)
> [ ... skip 23 lines ... ]
> + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, tpmSts, 1);
> + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
> + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
> + tpm_crb_clear_internal_buffers(s);
> + error_report("Command size '%d' less than TPM header size '%d'",
> + total_request_size, TPM_HEADER_SIZE);
Use PRIu32 to avoid sign sign-mismatch
> [ ... skip 28 lines ... ]
> + 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);
Those lines are repeated below and could be factorized.
> @@ -152,20 +218,48 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
> [ ... skip 21 lines ... ]
> + 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, invoke, 1);
> + g_byte_array_set_size(s->response_buffer, s->be_buffer_size);
This pre-allocates the response buffer before the backend has written
anything. Since response_buffer->len > 0 now, a subsequent nextChunk
from the guest would enter tpm_crb_fill_command_response() and serve
uninitialized data.
Before this patch we would serve guest input data, which wasn't great either.
We could add a separate flag to track response readiness instead. This
will need to be migrated too.
> + 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) {
> + /*
> + * nextChunk is used both while sending and receiving data.
> + * To distinguish between the two, response_buffer is checked
Missing .
> + * 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);
Indentation <4
> + } else {
> + if (!tpm_crb_append_command_request(s)) {
> + break;
> + }
> + }
> + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
> + } else if (val & CRB_START_RESP_RETRY) {
> + if (s->response_buffer->len > 0) {
I'd suggest adding a trace here.
> @@ -205,13 +299,36 @@ static const MemoryRegionOps tpm_crb_memory_ops = {
> [ ... skip 15 lines ... ]
> +
> + /*
> + * Send the first chunk. Subsequent chunks will be sent using
> + * tpm_crb_fill_command_response()
> + */
> + uint32_t to_copy = MIN(CRB_CTRL_CMD_SIZE, s->response_buffer->len);
QEMU coding style: declaration not mixed with statements.
> + memcpy(mem, s->response_buffer->data, 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);
Consider factorizing
> + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
redundant clear
--
Marc-André Lureau <marcandre.lureau@redhat.com>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic
2026-03-26 11:27 ` marcandre.lureau
@ 2026-03-31 17:07 ` Arun Menon
0 siblings, 0 replies; 21+ messages in thread
From: Arun Menon @ 2026-03-31 17:07 UTC (permalink / raw)
To: marcandre.lureau
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, Fabiano Rosas, Paolo Bonzini,
Igor Mammedov, Philippe Mathieu-Daudé, Yanan Wang,
Stefan Berger
Hi Marc-André,
Thank you for the review.
On Thu, Mar 26, 2026 at 03:27:02PM +0400, marcandre.lureau@redhat.com wrote:
> On Thu, 19 Mar 2026 19:23:13 +0530, Arun Menon <armenon@redhat.com> wrote:
>
> Hi,
>
> >
> > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> > index 5ea1a4a9706..e61c04aee0b 100644
> > --- a/hw/tpm/tpm_crb.c
> > +++ b/hw/tpm/tpm_crb.c
> > @@ -80,6 +82,8 @@ enum crb_ctrl_req {
> >
> > enum crb_start {
> > CRB_START_INVOKE = BIT(0),
> > + CRB_START_RESP_RETRY = BIT(1),
>
> Hmm, maybe we should rename that field, since it is "Start" in the spec (it changed?).
The Start has not changed from before.
Yes, I shall make changes to the other 2.
>
> > + CRB_START_NEXT_CHUNK = BIT(2),
> > };
>
> And follow the spec naming here "RspRetry" -> RSP_RETRY
>
> > @@ -122,6 +126,68 @@ static uint8_t tpm_crb_get_active_locty(CRBState *s)
> > [ ... skip 23 lines ... ]
> > + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, tpmSts, 1);
> > + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
> > + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
> > + tpm_crb_clear_internal_buffers(s);
> > + error_report("Command size '%d' less than TPM header size '%d'",
> > + total_request_size, TPM_HEADER_SIZE);
>
> Use PRIu32 to avoid sign sign-mismatch
Yes. Will do.
>
> > [ ... skip 28 lines ... ]
> > + 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);
>
> Those lines are repeated below and could be factorized.
Yes, I will move it into a separate function
>
> > @@ -152,20 +218,48 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
> > [ ... skip 21 lines ... ]
> > + 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, invoke, 1);
> > + g_byte_array_set_size(s->response_buffer, s->be_buffer_size);
>
> This pre-allocates the response buffer before the backend has written
> anything. Since response_buffer->len > 0 now, a subsequent nextChunk
> from the guest would enter tpm_crb_fill_command_response() and serve
> uninitialized data.
>
> Before this patch we would serve guest input data, which wasn't great either.
>
>
> We could add a separate flag to track response readiness instead. This
> will need to be migrated too.
Good catch. I did not think of what happens when the guest issues a
subsequent nextChunk while the tpm backend has not yet processed the
command.
I am wondering, checking if the start invoke bit is set will alone
prevent such a situation in the first place.
For example adding the following at the beginning,
case A_CRB_CTRL_START:
if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
break;
}
That way, a subsequent nextChunk or rspRetry will not be processed in
the first place. Do you see any problem with this approach? This will
also allow us to keep the migration logic as is.
>
> > + 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) {
> > + /*
> > + * nextChunk is used both while sending and receiving data.
> > + * To distinguish between the two, response_buffer is checked
>
> Missing .
Will correct.
>
> > + * 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);
>
> Indentation <4
Yes.
>
> > + } else {
> > + if (!tpm_crb_append_command_request(s)) {
> > + break;
> > + }
> > + }
> > + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
> > + } else if (val & CRB_START_RESP_RETRY) {
> > + if (s->response_buffer->len > 0) {
>
> I'd suggest adding a trace here.
Sure, will add.
>
> > @@ -205,13 +299,36 @@ static const MemoryRegionOps tpm_crb_memory_ops = {
> > [ ... skip 15 lines ... ]
> > +
> > + /*
> > + * Send the first chunk. Subsequent chunks will be sent using
> > + * tpm_crb_fill_command_response()
> > + */
> > + uint32_t to_copy = MIN(CRB_CTRL_CMD_SIZE, s->response_buffer->len);
>
> QEMU coding style: declaration not mixed with statements.
I will add this to the top of the block.
>
> > + memcpy(mem, s->response_buffer->data, 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);
>
> Consider factorizing
>
> > + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, invoke, 0);
>
> redundant clear
yes, shall remove.
>
> --
> Marc-André Lureau <marcandre.lureau@redhat.com>
>
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC v2 5/7] test/qtest: Add test for tpm crb chunking
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (3 preceding siblings ...)
2026-03-19 13:53 ` [RFC v2 4/7] hw/tpm: Implement TPM CRB chunking logic Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-26 11:27 ` marcandre.lureau
2026-03-27 22:10 ` Stefan Berger
2026-03-19 13:53 ` [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
2026-03-19 13:53 ` [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192 Arun Menon
6 siblings, 2 replies; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon
- 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()
Signed-off-by: Arun Menon <armenon@redhat.com>
---
tests/qtest/tpm-crb-swtpm-test.c | 10 +++
tests/qtest/tpm-util.c | 106 ++++++++++++++++++++++++++-----
tests/qtest/tpm-util.h | 5 ++
3 files changed, 106 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..dfc1fd70b7 100644
--- a/tests/qtest/tpm-util.c
+++ b/tests/qtest/tpm-util.c
@@ -14,16 +14,42 @@
#include "qemu/osdep.h"
#include <glib/gstdio.h>
+#include "system/memory.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_CTRL_STS (TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS)
+#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE)
+
+#define BIT_START_INVOKE (1 << 0)
+#define BIT_RETRY_RESPONSE (1 << 1)
+#define BIT_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) {
+ 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 tpmSts;
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 +57,74 @@ 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, BIT_START_INVOKE);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, BIT_START_INVOKE);
+
+ tpmSts = qtest_readl(s, CRB_CTRL_STS);
+ g_assert_cmpint(tpmSts & 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 tpmSts;
+
+ 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_CTRL_CMD_SIZE);
+
+ size_t 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 , BIT_START_INVOKE);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, BIT_START_INVOKE);
+ } else {
+ qtest_writel(s, CRB_ADDR_START , BIT_NEXT_CHUNK);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, BIT_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);
+ }
+ tpmSts = qtest_readl(s, CRB_CTRL_STS);
+ g_assert_cmpint(tpmSts & 1, ==, 0);
- qtest_memread(s, raddr, rsp, rsp_size);
+ /*
+ * Read response in chunks
+ */
+
+ unsigned char header[10];
+ qtest_memread(s, raddr, header, sizeof(header));
+
+ uint32_t 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, BIT_NEXT_CHUNK);
+ tpm_wait_till_bit_clear(s, CRB_ADDR_START, BIT_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] 21+ messages in thread* Re: [RFC v2 5/7] test/qtest: Add test for tpm crb chunking
2026-03-19 13:53 ` [RFC v2 5/7] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-03-26 11:27 ` marcandre.lureau
2026-03-26 11:32 ` Marc-André Lureau
2026-03-27 22:10 ` Stefan Berger
1 sibling, 1 reply; 21+ messages in thread
From: marcandre.lureau @ 2026-03-26 11:27 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On Thu, 19 Mar 2026 19:23:14 +0530, Arun Menon <armenon@redhat.com> wrote:
Hi
>
> diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
> index 2cb2dd47961..dfc1fd70b7f 100644
> --- a/tests/qtest/tpm-util.c
> +++ b/tests/qtest/tpm-util.c
> @@ -14,16 +14,42 @@
>
> #include "qemu/osdep.h"
> #include <glib/gstdio.h>
> +#include "system/memory.h"
Use qemu/bswap.h instead
>
> #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_CTRL_STS (TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS)
> +#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE)
Shadows tpm.h macro. Use CRB_ADDR_CTRL_STS ?
> +
> +#define BIT_START_INVOKE (1 << 0)
Similar (although in tpm_crb.c unit)
> [ ... skip 11 lines ... ]
> + break;
> + }
> + if (g_get_monotonic_time() >= end_time) {
> + break;
> + }
> + }
Consider adding a g_assert() here
> @@ -31,24 +57,74 @@ void tpm_util_crb_transfer(QTestState *s,
> [ ... skip 30 lines ... ]
> +
> + 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 ;
Trailing whitespace before the semicolon.
> +
> + 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 , BIT_START_INVOKE);
Trailing whitespace before the comma.
Style: should be tpm_sts per QEMU naming conventions.
Style: declarations should be at the top of the function.
Same: declaration mixed with statements.
Style: should be tpm_sts per QEMU naming conventions.
Style: declarations should be at the top of the function.
Same: declaration mixed with statements.
--
Marc-André Lureau <marcandre.lureau@redhat.com>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 5/7] test/qtest: Add test for tpm crb chunking
2026-03-26 11:27 ` marcandre.lureau
@ 2026-03-26 11:32 ` Marc-André Lureau
0 siblings, 0 replies; 21+ messages in thread
From: Marc-André Lureau @ 2026-03-26 11:32 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, Fabiano Rosas, Paolo Bonzini,
Igor Mammedov, Philippe Mathieu-Daudé, Yanan Wang
Hi
On Thu, Mar 26, 2026 at 3:27 PM <marcandre.lureau@redhat.com> wrote:
>
> On Thu, 19 Mar 2026 19:23:14 +0530, Arun Menon <armenon@redhat.com> wrote:
>
> Hi
>
> >
> > diff --git a/tests/qtest/tpm-util.c b/tests/qtest/tpm-util.c
> > index 2cb2dd47961..dfc1fd70b7f 100644
> > --- a/tests/qtest/tpm-util.c
> > +++ b/tests/qtest/tpm-util.c
> > @@ -14,16 +14,42 @@
> >
> > #include "qemu/osdep.h"
> > #include <glib/gstdio.h>
> > +#include "system/memory.h"
>
> Use qemu/bswap.h instead
>
> >
> > #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_CTRL_STS (TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS)
> > +#define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE)
>
> Shadows tpm.h macro. Use CRB_ADDR_CTRL_STS ?
>
> > +
> > +#define BIT_START_INVOKE (1 << 0)
>
> Similar (although in tpm_crb.c unit)
>
> > [ ... skip 11 lines ... ]
> > + break;
> > + }
> > + if (g_get_monotonic_time() >= end_time) {
> > + break;
> > + }
> > + }
>
> Consider adding a g_assert() here
>
> > @@ -31,24 +57,74 @@ void tpm_util_crb_transfer(QTestState *s,
> > [ ... skip 30 lines ... ]
> > +
> > + 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 ;
>
> Trailing whitespace before the semicolon.
>
> > +
> > + 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 , BIT_START_INVOKE);
>
> Trailing whitespace before the comma.
>
> Style: should be tpm_sts per QEMU naming conventions.
> Style: declarations should be at the top of the function.
> Same: declaration mixed with statements.
>
> Style: should be tpm_sts per QEMU naming conventions.
> Style: declarations should be at the top of the function.
> Same: declaration mixed with statements.
>
This is the first time I used "b4 review tui", this is strange. I'll
check what happened :)
I'd recommend others to try it, especially those like me who lack a
good "all-in-terminal" workflow for reviews.
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC v2 5/7] test/qtest: Add test for tpm crb chunking
2026-03-19 13:53 ` [RFC v2 5/7] test/qtest: Add test for tpm crb chunking Arun Menon
2026-03-26 11:27 ` marcandre.lureau
@ 2026-03-27 22:10 ` Stefan Berger
1 sibling, 0 replies; 21+ messages in thread
From: Stefan Berger @ 2026-03-27 22:10 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On 3/19/26 9:53 AM, Arun Menon wrote:
> - 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()
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
FYI: Based on this test here I have now created two more patches that:
- allow starting swtpm with a profile
- extends your test case to check whether default-v2 profile (with
ML-DSA support) is available
- creates an ML-DSA-87 key
- WIP: the test case will have to do a signing operation with the key to
receive the >4k signature; I don't have a tool yet to create the
necessary command byte stream(s), so this is work-in-progress
https://github.com/stefanberger/qemu-tpm/commits/crb-chunking/
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (4 preceding siblings ...)
2026-03-19 13:53 ` [RFC v2 5/7] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-26 11:27 ` marcandre.lureau
2026-03-19 13:53 ` [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192 Arun Menon
6 siblings, 1 reply; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon
- Add subsection in VMState for TPM CRB with the newly introduced
command and response buffers, along with a needed callback, so that
newer QEMU only sends the buffers if it is necessary.
- Add hw_compat blocker because the feature is only supported for
machine type 11.0 and higher.
- If the VM has no pending chunked TPM commands in the internal buffers
during a VM migration, or if the machine type does not support newly
introduced buffers, then the needed callback will return false, as it
checks the hw_compat blocker and thus the subsection will not be sent
to the destination host.
- Since the original command and response buffers are of type GByteArray,
they are serialized in pre-save hook before sending them to the
destination host and then restored back into original buffer using
the post-load hook.
Signed-off-by: Arun Menon <armenon@redhat.com>
---
hw/core/machine.c | 1 +
hw/tpm/tpm_crb.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 115 insertions(+)
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 6cf0e2f404..fcd6043c99 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -40,6 +40,7 @@
GlobalProperty hw_compat_10_2[] = {
{ "scsi-block", "migrate-pr", "off" },
+ { "tpm-crb", "migrate-buffers", "off"},
};
const size_t hw_compat_10_2_len = G_N_ELEMENTS(hw_compat_10_2);
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index e61c04aee0..9ce342fe8a 100644
--- a/hw/tpm/tpm_crb.c
+++ b/hw/tpm/tpm_crb.c
@@ -33,6 +33,17 @@
#include "trace.h"
#include "qom/object.h"
+/* command and response buffers; part of VM state when migrating */
+typedef struct TPMCRBMigState {
+ uint32_t cmd_size;
+ uint8_t *cmd_tmp;
+
+ uint32_t rsp_size;
+ uint8_t *rsp_tmp;
+
+ uint32_t rsp_offset;
+} TPMCRBMigState;
+
struct CRBState {
DeviceState parent_obj;
@@ -49,6 +60,9 @@ struct CRBState {
bool ppi_enabled;
TPMPPI ppi;
+
+ bool migrate_buffers;
+ TPMCRBMigState mig;
};
typedef struct CRBState CRBState;
@@ -347,18 +361,118 @@ static int tpm_crb_pre_save(void *opaque)
return 0;
}
+static bool tpm_crb_chunk_needed(void *opaque)
+{
+ CRBState *s = opaque;
+
+ if (!s->migrate_buffers) {
+ return false;
+ }
+
+ return ((s->command_buffer && s->command_buffer->len > 0) ||
+ (s->response_buffer && s->response_buffer->len > 0));
+}
+
+static int tpm_crb_chunk_pre_save(void *opaque)
+{
+ CRBState *s = opaque;
+
+ if (s->command_buffer) {
+ s->mig.cmd_size = s->command_buffer->len;
+ s->mig.cmd_tmp = s->command_buffer->data;
+ } else {
+ s->mig.cmd_tmp = NULL;
+ s->mig.cmd_size = 0;
+ }
+
+ if (s->response_buffer) {
+ s->mig.rsp_size = s->response_buffer->len;
+ s->mig.rsp_tmp = s->response_buffer->data;
+ } else {
+ s->mig.rsp_tmp = NULL;
+ s->mig.rsp_size = 0;
+ }
+ s->mig.rsp_offset = (uint32_t)s->response_offset;
+ return 0;
+}
+
+static bool tpm_crb_chunk_post_load(void *opaque, int version_id, Error **errp)
+{
+ CRBState *s = opaque;
+
+ if (s->mig.cmd_size > s->be_buffer_size ||
+ s->mig.rsp_size > s->be_buffer_size ||
+ s->mig.rsp_offset > s->mig.rsp_size) {
+ error_setg(errp,
+ "tpm-crb-chunk: incoming buffer %u, exceeds limits %zu "
+ "or offset %u exceeds size %u",
+ s->mig.cmd_size, s->be_buffer_size,
+ s->mig.rsp_offset, s->mig.rsp_size);
+ g_free(s->mig.cmd_tmp);
+ s->mig.cmd_tmp = NULL;
+ g_free(s->mig.rsp_tmp);
+ s->mig.rsp_tmp = NULL;
+ return false;
+ }
+
+ if (s->mig.cmd_tmp) {
+ if (s->command_buffer) {
+ g_byte_array_unref(s->command_buffer);
+ }
+ s->command_buffer = g_byte_array_new_take(s->mig.cmd_tmp,
+ s->mig.cmd_size);
+ s->mig.cmd_tmp = NULL;
+ } else {
+ if (s->command_buffer) {
+ g_byte_array_set_size(s->command_buffer, 0);
+ }
+ }
+ if (s->mig.rsp_tmp) {
+ if (s->response_buffer) {
+ g_byte_array_unref(s->response_buffer);
+ }
+ s->response_buffer = g_byte_array_new_take(s->mig.rsp_tmp,
+ s->mig.rsp_size);
+ s->mig.rsp_tmp = NULL;
+ }
+ return true;
+}
+
+static const VMStateDescription vmstate_tpm_crb_chunk = {
+ .name = "tpm-crb/chunk",
+ .version_id = 1,
+ .needed = tpm_crb_chunk_needed,
+ .pre_save = tpm_crb_chunk_pre_save,
+ .post_load_errp = tpm_crb_chunk_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(mig.cmd_size, CRBState),
+ VMSTATE_VBUFFER_ALLOC_UINT32(mig.cmd_tmp, CRBState, 0, NULL,
+ mig.cmd_size),
+ VMSTATE_UINT32(mig.rsp_size, CRBState),
+ VMSTATE_VBUFFER_ALLOC_UINT32(mig.rsp_tmp, CRBState, 0, NULL,
+ mig.rsp_size),
+ VMSTATE_UINT32(mig.rsp_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,
}
};
static const Property tpm_crb_properties[] = {
DEFINE_PROP_TPMBE("tpmdev", CRBState, tpmbe),
DEFINE_PROP_BOOL("ppi", CRBState, ppi_enabled, true),
+ DEFINE_PROP_BOOL("migrate-buffers", CRBState, migrate_buffers, true),
};
static void tpm_crb_reset(void *dev)
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-03-19 13:53 ` [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
@ 2026-03-26 11:27 ` marcandre.lureau
2026-04-02 15:22 ` Arun Menon
0 siblings, 1 reply; 21+ messages in thread
From: marcandre.lureau @ 2026-03-26 11:27 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On Thu, 19 Mar 2026 19:23:15 +0530, Arun Menon <armenon@redhat.com> wrote:
Hi
>
> diff --git a/hw/core/machine.c b/hw/core/machine.c
> index 6cf0e2f404e..fcd6043c992 100644
> --- a/hw/core/machine.c
> +++ b/hw/core/machine.c
> @@ -40,6 +40,7 @@
>
> GlobalProperty hw_compat_10_2[] = {
Let's not forget to update this to 11.0 for 11.1 :)
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index e61c04aee0b..9ce342fe8ac 100644
> --- a/hw/tpm/tpm_crb.c
> +++ b/hw/tpm/tpm_crb.c
> @@ -33,6 +33,17 @@
> #include "trace.h"
> #include "qom/object.h"
>
> +/* command and response buffers; part of VM state when migrating */
> +typedef struct TPMCRBMigState {
> + uint32_t cmd_size;
> + uint8_t *cmd_tmp;
Could we name them after the name of the state fields?
command_buffer_len && command_buffer_data ?
> +
> + uint32_t rsp_size;
> + uint8_t *rsp_tmp;
(response_buffer_len ..)
Actually, you should not need an extra MigState at all. Check how
GByteArray are handled in in ui/vdagent.c for example. (I have a doubt
that it may eventually leak if loading for a running state, where data
!= NULL though, I would have to check - we may want to use realloc for
buffers)
> @@ -347,18 +361,118 @@ static int tpm_crb_pre_save(void *opaque)
> [ ... skip 30 lines ... ]
> + } else {
> + s->mig.rsp_tmp = NULL;
> + s->mig.rsp_size = 0;
> + }
> + s->mig.rsp_offset = (uint32_t)s->response_offset;
> + return 0;
If you get rid of MigState, the original response_offset field will
proably have to be uint32_t too.
> [ ... skip 14 lines ... ]
> + g_free(s->mig.cmd_tmp);
> + s->mig.cmd_tmp = NULL;
> + g_free(s->mig.rsp_tmp);
> + s->mig.rsp_tmp = NULL;
> + return false;
> + }
Suggesting g_clear_pointer(&ptr, g_free)
> +
> + if (s->mig.cmd_tmp) {
> + if (s->command_buffer) {
> + g_byte_array_unref(s->command_buffer);
> + }
Slightly neater:
g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> + s->command_buffer = g_byte_array_new_take(s->mig.cmd_tmp,
> + s->mig.cmd_size);
> + s->mig.cmd_tmp = NULL;
> + } else {
> + if (s->command_buffer) {
> + g_byte_array_set_size(s->command_buffer, 0);
> + }
> + }
> + if (s->mig.rsp_tmp) {
> + if (s->response_buffer) {
> + g_byte_array_unref(s->response_buffer);
> + }
Or you could simply without condition:
g_clear_pointer(&s->command_buffer, g_byte_array_unref);
s->command_buffer = g_byte_array_new_take(s->mig.cmd_tmp, s->mig.cmd_size);
This will also reset the size to 0 if cmd_tmp is NULL and cmd_size is 0.
> + s->response_buffer = g_byte_array_new_take(s->mig.rsp_tmp,
> + s->mig.rsp_size);
> + s->mig.rsp_tmp = NULL;
> + }
Missing
s->response_offset = s->mig.rsp_offset;
> + return true;
> +}
> +
> +static const VMStateDescription vmstate_tpm_crb_chunk = {
> + .name = "tpm-crb/chunk",
> + .version_id = 1,
> + .needed = tpm_crb_chunk_needed,
> + .pre_save = tpm_crb_chunk_pre_save,
> + .post_load_errp = tpm_crb_chunk_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(mig.cmd_size, CRBState),
Could be version 0, I think.
--
Marc-André Lureau <marcandre.lureau@redhat.com>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking
2026-03-26 11:27 ` marcandre.lureau
@ 2026-04-02 15:22 ` Arun Menon
0 siblings, 0 replies; 21+ messages in thread
From: Arun Menon @ 2026-04-02 15:22 UTC (permalink / raw)
To: marcandre.lureau
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, Fabiano Rosas, Paolo Bonzini,
Igor Mammedov, Philippe Mathieu-Daudé, Yanan Wang
On Thu, Mar 26, 2026 at 03:27:02PM +0400, marcandre.lureau@redhat.com wrote:
> On Thu, 19 Mar 2026 19:23:15 +0530, Arun Menon <armenon@redhat.com> wrote:
>
> Hi
>
> >
> > diff --git a/hw/core/machine.c b/hw/core/machine.c
> > index 6cf0e2f404e..fcd6043c992 100644
> > --- a/hw/core/machine.c
> > +++ b/hw/core/machine.c
> > @@ -40,6 +40,7 @@
> >
> > GlobalProperty hw_compat_10_2[] = {
>
> Let's not forget to update this to 11.0 for 11.1 :)
Yes I shall base it on top of 20260331140347.653404-1-cohuck@redhat.com
>
> >
> > diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> > index e61c04aee0b..9ce342fe8ac 100644
> > --- a/hw/tpm/tpm_crb.c
> > +++ b/hw/tpm/tpm_crb.c
> > @@ -33,6 +33,17 @@
> > #include "trace.h"
> > #include "qom/object.h"
> >
> > +/* command and response buffers; part of VM state when migrating */
> > +typedef struct TPMCRBMigState {
> > + uint32_t cmd_size;
> > + uint8_t *cmd_tmp;
>
> Could we name them after the name of the state fields?
> command_buffer_len && command_buffer_data ?
This will be not needed.
>
> > +
> > + uint32_t rsp_size;
> > + uint8_t *rsp_tmp;
>
> (response_buffer_len ..)
>
> Actually, you should not need an extra MigState at all. Check how
> GByteArray are handled in in ui/vdagent.c for example. (I have a doubt
> that it may eventually leak if loading for a running state, where data
> != NULL though, I would have to check - we may want to use realloc for
> buffers)
I am thinking of adding a separate VMStateInfo for GByteArray. The .get
and .put functions will call actual GLib API to set the size of the
buffer or create a new one if the existing data == NULL.
const VMStateInfo vmstate_info_g_byte_array = {
.name = "GByteArray",
.get = get_g_byte_array, // Creates a new GLib object iff data!=NULL
.put = put_g_byte_array,
}
We will not require any pre-save and post load hooks in that case.
We can also use it in the ui/vdagent.c.
>
> > @@ -347,18 +361,118 @@ static int tpm_crb_pre_save(void *opaque)
> > [ ... skip 30 lines ... ]
> > + } else {
> > + s->mig.rsp_tmp = NULL;
> > + s->mig.rsp_size = 0;
> > + }
> > + s->mig.rsp_offset = (uint32_t)s->response_offset;
> > + return 0;
>
> If you get rid of MigState, the original response_offset field will
> proably have to be uint32_t too.
>
> > [ ... skip 14 lines ... ]
> > + g_free(s->mig.cmd_tmp);
> > + s->mig.cmd_tmp = NULL;
> > + g_free(s->mig.rsp_tmp);
> > + s->mig.rsp_tmp = NULL;
> > + return false;
> > + }
>
> Suggesting g_clear_pointer(&ptr, g_free)
>
> > +
> > + if (s->mig.cmd_tmp) {
> > + if (s->command_buffer) {
> > + g_byte_array_unref(s->command_buffer);
> > + }
>
> Slightly neater:
> g_clear_pointer(&s->command_buffer, g_byte_array_unref);
>
> > + s->command_buffer = g_byte_array_new_take(s->mig.cmd_tmp,
> > + s->mig.cmd_size);
> > + s->mig.cmd_tmp = NULL;
> > + } else {
> > + if (s->command_buffer) {
> > + g_byte_array_set_size(s->command_buffer, 0);
> > + }
> > + }
> > + if (s->mig.rsp_tmp) {
> > + if (s->response_buffer) {
> > + g_byte_array_unref(s->response_buffer);
> > + }
>
> Or you could simply without condition:
>
> g_clear_pointer(&s->command_buffer, g_byte_array_unref);
> s->command_buffer = g_byte_array_new_take(s->mig.cmd_tmp, s->mig.cmd_size);
>
> This will also reset the size to 0 if cmd_tmp is NULL and cmd_size is 0.
>
> > + s->response_buffer = g_byte_array_new_take(s->mig.rsp_tmp,
> > + s->mig.rsp_size);
> > + s->mig.rsp_tmp = NULL;
> > + }
>
> Missing
> s->response_offset = s->mig.rsp_offset;
>
> > + return true;
> > +}
> > +
> > +static const VMStateDescription vmstate_tpm_crb_chunk = {
> > + .name = "tpm-crb/chunk",
> > + .version_id = 1,
> > + .needed = tpm_crb_chunk_needed,
> > + .pre_save = tpm_crb_chunk_pre_save,
> > + .post_load_errp = tpm_crb_chunk_post_load,
> > + .fields = (const VMStateField[]) {
> > + VMSTATE_UINT32(mig.cmd_size, CRBState),
>
> Could be version 0, I think.
Yes, will change it
>
> --
> Marc-André Lureau <marcandre.lureau@redhat.com>
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 21+ messages in thread
* [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-03-19 13:53 [RFC v2 0/7] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (5 preceding siblings ...)
2026-03-19 13:53 ` [RFC v2 6/7] hw/tpm: Add support for VM migration with TPM CRB chunking Arun Menon
@ 2026-03-19 13:53 ` Arun Menon
2026-03-20 18:57 ` Stefan Berger
6 siblings, 1 reply; 21+ messages in thread
From: Arun Menon @ 2026-03-19 13:53 UTC (permalink / raw)
To: qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang, Arun Menon
- Double the size from 4096 to 8192 so that we can have bigger buffer
enabling support for PQC algorithms in the TPM TIS interface.
- v185 of TCG TPM rolls out PQC algorithm support. [1]
[1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/document/previewpdf/45151
Signed-off-by: Arun Menon <armenon@redhat.com>
---
hw/tpm/tpm_tis.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
index 184632ff66..0df35d5a54 100644
--- a/hw/tpm/tpm_tis.h
+++ b/hw/tpm/tpm_tis.h
@@ -33,7 +33,7 @@
#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
-#define TPM_TIS_BUFFER_MAX 4096
+#define TPM_TIS_BUFFER_MAX 8192
typedef enum {
TPM_TIS_STATE_IDLE = 0,
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-03-19 13:53 ` [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192 Arun Menon
@ 2026-03-20 18:57 ` Stefan Berger
2026-03-31 19:31 ` Stefan Berger
0 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2026-03-20 18:57 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On 3/19/26 9:53 AM, Arun Menon wrote:
> - Double the size from 4096 to 8192 so that we can have bigger buffer
> enabling support for PQC algorithms in the TPM TIS interface.
> - v185 of TCG TPM rolls out PQC algorithm support. [1]
>
> [1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/document/previewpdf/45151
>
> Signed-off-by: Arun Menon <armenon@redhat.com>
> ---
> hw/tpm/tpm_tis.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
> index 184632ff66..0df35d5a54 100644
> --- a/hw/tpm/tpm_tis.h
> +++ b/hw/tpm/tpm_tis.h
> @@ -33,7 +33,7 @@
>
> #define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
>
> -#define TPM_TIS_BUFFER_MAX 4096
> +#define TPM_TIS_BUFFER_MAX 8192
Unfortunately TIS uses a fixed-size buffer that would now become bigger:
typedef struct TPMState {
MemoryRegion mmio;
unsigned char buffer[TPM_TIS_BUFFER_MAX]; <-- now 8192; before 4096
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), <-- now 8192;
before 4096
VMSTATE_UINT16(state.rw_offset, TPMStateISA),
Problem would be if an older version of the TIS (with size 4096) then
receives this 8192 buffer, we would (probably) get a buffer overflow.
>
> typedef enum {
> TPM_TIS_STATE_IDLE = 0,
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-03-20 18:57 ` Stefan Berger
@ 2026-03-31 19:31 ` Stefan Berger
2026-04-01 5:44 ` Arun Menon
0 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2026-03-31 19:31 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On 3/20/26 2:57 PM, Stefan Berger wrote:
>
>
> On 3/19/26 9:53 AM, Arun Menon wrote:
>> - Double the size from 4096 to 8192 so that we can have bigger buffer
>> enabling support for PQC algorithms in the TPM TIS interface.
>> - v185 of TCG TPM rolls out PQC algorithm support. [1]
>>
>> [1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/
>> document/previewpdf/45151
>>
>> Signed-off-by: Arun Menon <armenon@redhat.com>
>> ---
>> hw/tpm/tpm_tis.h | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
>> index 184632ff66..0df35d5a54 100644
>> --- a/hw/tpm/tpm_tis.h
>> +++ b/hw/tpm/tpm_tis.h
>> @@ -33,7 +33,7 @@
>> #define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
>> -#define TPM_TIS_BUFFER_MAX 4096
>> +#define TPM_TIS_BUFFER_MAX 8192
>
> Unfortunately TIS uses a fixed-size buffer that would now become bigger:
>
> typedef struct TPMState {
> MemoryRegion mmio;
>
> unsigned char buffer[TPM_TIS_BUFFER_MAX]; <-- now 8192; before 4096
>
>
> 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), <-- now 8192;
> before 4096
This will have to become VMSTATE_PARTIAL_BUFFER and the rest is saved
with VMSTATE_BUFFER_START_MIDDLE if necessary.
> VMSTATE_UINT16(state.rw_offset, TPMStateISA),
>
> Problem would be if an older version of the TIS (with size 4096) then
> receives this 8192 buffer, we would (probably) get a buffer overflow.
I created 2 more patches for the TIS. It's now also in my branch here:
https://github.com/stefanberger/qemu-tpm/commits/crb-chunking/
Both TIS and CRB can now transfer >4096 bytes packets.
>
>
>
>> typedef enum {
>> TPM_TIS_STATE_IDLE = 0,
>
>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-03-31 19:31 ` Stefan Berger
@ 2026-04-01 5:44 ` Arun Menon
2026-04-01 13:43 ` Stefan Berger
0 siblings, 1 reply; 21+ messages in thread
From: Arun Menon @ 2026-04-01 5:44 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
Hi Stefan,
Thank you for looking into this and providing with the additional
patches to handle TIS interface.
On Tue, Mar 31, 2026 at 03:31:43PM -0400, Stefan Berger wrote:
>
>
> On 3/20/26 2:57 PM, Stefan Berger wrote:
> >
> >
> > On 3/19/26 9:53 AM, Arun Menon wrote:
> > > - Double the size from 4096 to 8192 so that we can have bigger buffer
> > > enabling support for PQC algorithms in the TPM TIS interface.
> > > - v185 of TCG TPM rolls out PQC algorithm support. [1]
> > >
> > > [1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/
> > > document/previewpdf/45151
> > >
> > > Signed-off-by: Arun Menon <armenon@redhat.com>
> > > ---
> > > hw/tpm/tpm_tis.h | 2 +-
> > > 1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
> > > index 184632ff66..0df35d5a54 100644
> > > --- a/hw/tpm/tpm_tis.h
> > > +++ b/hw/tpm/tpm_tis.h
> > > @@ -33,7 +33,7 @@
> > > #define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
> > > -#define TPM_TIS_BUFFER_MAX 4096
> > > +#define TPM_TIS_BUFFER_MAX 8192
> >
> > Unfortunately TIS uses a fixed-size buffer that would now become bigger:
> >
> > typedef struct TPMState {
> > MemoryRegion mmio;
> >
> > unsigned char buffer[TPM_TIS_BUFFER_MAX]; <-- now 8192; before 4096
> >
> >
> > 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), <-- now 8192;
> > before 4096
>
> This will have to become VMSTATE_PARTIAL_BUFFER and the rest is saved with
> VMSTATE_BUFFER_START_MIDDLE if necessary.
>
> > VMSTATE_UINT16(state.rw_offset, TPMStateISA),
> >
> > Problem would be if an older version of the TIS (with size 4096) then
> > receives this 8192 buffer, we would (probably) get a buffer overflow.
>
> I created 2 more patches for the TIS. It's now also in my branch here:
>
> https://github.com/stefanberger/qemu-tpm/commits/crb-chunking/
Looks good to me.
>
> Both TIS and CRB can now transfer >4096 bytes packets.
>
> >
> >
> >
> > > typedef enum {
> > > TPM_TIS_STATE_IDLE = 0,
> >
> >
>
Is it okay if I incorporate your commits, including the tests and
profile enabling support into my next revision of this series?
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-04-01 5:44 ` Arun Menon
@ 2026-04-01 13:43 ` Stefan Berger
2026-04-01 14:05 ` Stefan Berger
0 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2026-04-01 13:43 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On 4/1/26 1:44 AM, Arun Menon wrote:
> Hi Stefan,
>
> Thank you for looking into this and providing with the additional
> patches to handle TIS interface.
>
> On Tue, Mar 31, 2026 at 03:31:43PM -0400, Stefan Berger wrote:
>>
>>
>> On 3/20/26 2:57 PM, Stefan Berger wrote:
>>>
>>>
>>> On 3/19/26 9:53 AM, Arun Menon wrote:
>>>> - Double the size from 4096 to 8192 so that we can have bigger buffer
>>>> enabling support for PQC algorithms in the TPM TIS interface.
>>>> - v185 of TCG TPM rolls out PQC algorithm support. [1]
>>>>
>>>> [1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/
>>>> document/previewpdf/45151
>>>>
>>>> Signed-off-by: Arun Menon <armenon@redhat.com>
>>>> ---
>>>> hw/tpm/tpm_tis.h | 2 +-
>>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>>
>>>> diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
>>>> index 184632ff66..0df35d5a54 100644
>>>> --- a/hw/tpm/tpm_tis.h
>>>> +++ b/hw/tpm/tpm_tis.h
>>>> @@ -33,7 +33,7 @@
>>>> #define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
>>>> -#define TPM_TIS_BUFFER_MAX 4096
>>>> +#define TPM_TIS_BUFFER_MAX 8192
>>>
>>> Unfortunately TIS uses a fixed-size buffer that would now become bigger:
>>>
>>> typedef struct TPMState {
>>> MemoryRegion mmio;
>>>
>>> unsigned char buffer[TPM_TIS_BUFFER_MAX]; <-- now 8192; before 4096
>>>
>>>
>>> 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), <-- now 8192;
>>> before 4096
>>
>> This will have to become VMSTATE_PARTIAL_BUFFER and the rest is saved with
>> VMSTATE_BUFFER_START_MIDDLE if necessary.
>>
>>> VMSTATE_UINT16(state.rw_offset, TPMStateISA),
>>>
>>> Problem would be if an older version of the TIS (with size 4096) then
>>> receives this 8192 buffer, we would (probably) get a buffer overflow.
>>
>> I created 2 more patches for the TIS. It's now also in my branch here:
>>
>> https://github.com/stefanberger/qemu-tpm/commits/crb-chunking/
>
> Looks good to me.
>
>>
>> Both TIS and CRB can now transfer >4096 bytes packets.
>>
>>>
>>>
>>>
>>>> typedef enum {
>>>> TPM_TIS_STATE_IDLE = 0,
>>>
>>>
>>
>
> Is it okay if I incorporate your commits, including the tests and
> profile enabling support into my next revision of this series?
Yes, that's okay. Unfortunately the test will not work for most people
right now. The default-v2 profile is available via swtpm in git master
but it doesn't support ML-DSA since libtpms code with PQC is not public,
yet...
>
>
>
> Regards,
> Arun Menon
>
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [RFC v2 7/7] hw/tpm: Increase TPM TIS max buffer size to 8192
2026-04-01 13:43 ` Stefan Berger
@ 2026-04-01 14:05 ` Stefan Berger
0 siblings, 0 replies; 21+ messages in thread
From: Stefan Berger @ 2026-04-01 14:05 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Ani Sinha, Marcel Apfelbaum, Laurent Vivier, Zhao Liu,
Michael S. Tsirkin, Stefan Berger, marcandre.lureau,
Fabiano Rosas, Paolo Bonzini, Igor Mammedov,
Philippe Mathieu-Daudé, Yanan Wang
On 4/1/26 9:43 AM, Stefan Berger wrote:
>
>
> On 4/1/26 1:44 AM, Arun Menon wrote:
>> Hi Stefan,
>>
>> Thank you for looking into this and providing with the additional
>> patches to handle TIS interface.
>>
>> On Tue, Mar 31, 2026 at 03:31:43PM -0400, Stefan Berger wrote:
>>>
>>>
>>> On 3/20/26 2:57 PM, Stefan Berger wrote:
>>>>
>>>>
>>>> On 3/19/26 9:53 AM, Arun Menon wrote:
>>>>> - Double the size from 4096 to 8192 so that we can have bigger buffer
>>>>> enabling support for PQC algorithms in the TPM TIS interface.
>>>>> - v185 of TCG TPM rolls out PQC algorithm support. [1]
>>>>>
>>>>> [1] section 46 https://members.trustedcomputinggroup.org/wg/TCG/
>>>>> document/previewpdf/45151
>>>>>
>>>>> Signed-off-by: Arun Menon <armenon@redhat.com>
>>>>> ---
>>>>> hw/tpm/tpm_tis.h | 2 +-
>>>>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/hw/tpm/tpm_tis.h b/hw/tpm/tpm_tis.h
>>>>> index 184632ff66..0df35d5a54 100644
>>>>> --- a/hw/tpm/tpm_tis.h
>>>>> +++ b/hw/tpm/tpm_tis.h
>>>>> @@ -33,7 +33,7 @@
>>>>> #define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
>>>>> -#define TPM_TIS_BUFFER_MAX 4096
>>>>> +#define TPM_TIS_BUFFER_MAX 8192
>>>>
>>>> Unfortunately TIS uses a fixed-size buffer that would now become
>>>> bigger:
>>>>
>>>> typedef struct TPMState {
>>>> MemoryRegion mmio;
>>>>
>>>> unsigned char buffer[TPM_TIS_BUFFER_MAX]; <-- now 8192;
>>>> before 4096
>>>>
>>>>
>>>> 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), <-- now 8192;
>>>> before 4096
>>>
>>> This will have to become VMSTATE_PARTIAL_BUFFER and the rest is saved
>>> with
>>> VMSTATE_BUFFER_START_MIDDLE if necessary.
>>>
>>>> VMSTATE_UINT16(state.rw_offset, TPMStateISA),
>>>>
>>>> Problem would be if an older version of the TIS (with size 4096) then
>>>> receives this 8192 buffer, we would (probably) get a buffer overflow.
>>>
>>> I created 2 more patches for the TIS. It's now also in my branch here:
>>>
>>> https://github.com/stefanberger/qemu-tpm/commits/crb-chunking/
>>
>> Looks good to me.
>>
>>>
>>> Both TIS and CRB can now transfer >4096 bytes packets.
>>>
>>>>
>>>>
>>>>
>>>>> typedef enum {
>>>>> TPM_TIS_STATE_IDLE = 0,
>>>>
>>>>
>>>
>>
>> Is it okay if I incorporate your commits, including the tests and
>> profile enabling support into my next revision of this series?
>
> Yes, that's okay. Unfortunately the test will not work for most people
> right now. The default-v2 profile is available via swtpm in git master
> but it doesn't support ML-DSA since libtpms code with PQC is not public,
> yet...
I added a 2nd search criterion for the profile content to check whether
'ml-dsa' is supported. Fixed the style mistakes. Pushed. You can take
them from there now.
>
>>
>>
>>
>> Regards,
>> Arun Menon
>>
>
>
^ permalink raw reply [flat|nested] 21+ messages in thread