* [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC
@ 2026-03-12 4:16 Arun Menon
2026-03-12 4:16 ` [RFC 1/5] hw/tpm: Add TPM CRB chunking fields Arun Menon
` (5 more replies)
0 siblings, 6 replies; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
Arun Menon
The move to Post Quantum Cryptography (PQC) changes how we manage
memory buffers. Unlike classic crypto algorithms like RSA or ECC which
used small keys and signatures, PQC algorithms require larger buffers.
The new version of TCG TPM v185 (currently under review [1]) supports
sending data/commands in chunks for the CRB (Command Response Buffer)
interface. This is in line with the initiative to support PQC algorithms.
This series implements the logic to send and receive data from the
linux guest to the TPM backend in chunks, thereby allowing the
guest to send larger data buffers. We introduce 2 new control registers
called nextChunk and crbRspRetry that will control the START. We also
add the CRB Interface Identifier called CapCRBChunk that is set to 1
indicating that the device supports chunking. The default maximum
chunk/buffer size is 3968 (4096 - 128) bytes.
During a send operation, the guest driver places data in the CRB buffer
and signals nextChunk for each segment until the final chunk is reached.
Upon receiving the START signal, QEMU appends the final chunk to its
internal buffer and dispatches the complete command to the TPM backend.
For responses, the backend's output is buffered. The guest consumes the
first chunk once the START bit is cleared. Subsequent chunks are
retrieved by the guest toggling the nextChunk bit, which advances the
internal buffer offset and populates the CRB data window.
For this to work, the linux guest tpm driver will also have to
a) probe if CRB chunking is supported
b) send data in chunks if the command length exceeds the chunk size.
c) receive data in chunks by sending a nextChunk signal and accumulate.
At present, VM live migration during a tpm command execution in chunks
is not supported. We intend to add support for TIS interface in
the future.
The included test demonstrates functional correctness for standard
buffer sizes. However, validation of PQC-sized payloads was performed
via manual buffer-size overrides.
[1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
Arun Menon (5):
hw/tpm: Add TPM CRB chunking fields
hw/tpm: Refactor CRB_CTRL_START register access
hw/tpm: Add internal buffer state for chunking
hw/tpm: Implement TPM CRB chunking logic
test/qtest: Add test for tpm crb chunking
hw/tpm/tpm_crb.c | 167 +++++++++++++++++++++++++++----
include/hw/acpi/tpm.h | 5 +-
tests/qtest/tpm-crb-swtpm-test.c | 10 ++
tests/qtest/tpm-util.c | 106 +++++++++++++++++---
tests/qtest/tpm-util.h | 5 +
5 files changed, 256 insertions(+), 37 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [RFC 1/5] hw/tpm: Add TPM CRB chunking fields
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
@ 2026-03-12 4:16 ` Arun Menon
2026-03-13 12:28 ` Stefan Berger
2026-03-12 4:16 ` [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
` (4 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
Arun Menon
- 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>
---
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] 14+ messages in thread
* [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-03-12 4:16 ` [RFC 1/5] hw/tpm: Add TPM CRB chunking fields Arun Menon
@ 2026-03-12 4:16 ` Arun Menon
2026-03-13 12:33 ` Stefan Berger
2026-03-12 4:16 ` [RFC 3/5] hw/tpm: Add internal buffer state for chunking Arun Menon
` (3 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
Arun Menon
Replace manual bitwise operations with ARRAY_FIELD_DP32 macros
No functional changes.
Signed-off-by: Arun Menon <armenon@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 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] 14+ messages in thread
* [RFC 3/5] hw/tpm: Add internal buffer state for chunking
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-03-12 4:16 ` [RFC 1/5] hw/tpm: Add TPM CRB chunking fields Arun Menon
2026-03-12 4:16 ` [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
@ 2026-03-12 4:16 ` Arun Menon
2026-03-12 4:16 ` [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic Arun Menon
` (2 subsequent siblings)
5 siblings, 0 replies; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
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] 14+ messages in thread
* [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (2 preceding siblings ...)
2026-03-12 4:16 ` [RFC 3/5] hw/tpm: Add internal buffer state for chunking Arun Menon
@ 2026-03-12 4:16 ` Arun Menon
2026-03-13 17:55 ` Stefan Berger
2026-03-12 4:16 ` [RFC 5/5] test/qtest: Add test for tpm crb chunking Arun Menon
2026-03-12 14:36 ` [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Stefan Berger
5 siblings, 1 reply; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
Arun Menon
- 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>
---
hw/tpm/tpm_crb.c | 138 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 122 insertions(+), 16 deletions(-)
diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
index 5ea1a4a970..845f9c6c9f 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,58 @@ 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)
+{
+ 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)
+{
+ 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 +208,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 +289,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 +395,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] 14+ messages in thread
* [RFC 5/5] test/qtest: Add test for tpm crb chunking
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (3 preceding siblings ...)
2026-03-12 4:16 ` [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic Arun Menon
@ 2026-03-12 4:16 ` Arun Menon
2026-03-13 20:30 ` Stefan Berger
2026-03-12 14:36 ` [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Stefan Berger
5 siblings, 1 reply; 14+ messages in thread
From: Arun Menon @ 2026-03-12 4:16 UTC (permalink / raw)
To: qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov,
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..0a861a9e69 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] 14+ messages in thread
* Re: [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
` (4 preceding siblings ...)
2026-03-12 4:16 ` [RFC 5/5] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-03-12 14:36 ` Stefan Berger
2026-03-13 19:35 ` Arun Menon
5 siblings, 1 reply; 14+ messages in thread
From: Stefan Berger @ 2026-03-12 14:36 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov
On 3/12/26 12:16 AM, Arun Menon wrote:
> The move to Post Quantum Cryptography (PQC) changes how we manage
> memory buffers. Unlike classic crypto algorithms like RSA or ECC which
> used small keys and signatures, PQC algorithms require larger buffers.
>
> The new version of TCG TPM v185 (currently under review [1]) supports
> sending data/commands in chunks for the CRB (Command Response Buffer)
> interface. This is in line with the initiative to support PQC algorithms.
>
> This series implements the logic to send and receive data from the
> linux guest to the TPM backend in chunks, thereby allowing the
> guest to send larger data buffers. We introduce 2 new control registers
> called nextChunk and crbRspRetry that will control the START. We also
> add the CRB Interface Identifier called CapCRBChunk that is set to 1
> indicating that the device supports chunking. The default maximum
> chunk/buffer size is 3968 (4096 - 128) bytes.
>
> During a send operation, the guest driver places data in the CRB buffer
> and signals nextChunk for each segment until the final chunk is reached.
> Upon receiving the START signal, QEMU appends the final chunk to its
> internal buffer and dispatches the complete command to the TPM backend.
>
> For responses, the backend's output is buffered. The guest consumes the
> first chunk once the START bit is cleared. Subsequent chunks are
> retrieved by the guest toggling the nextChunk bit, which advances the
> internal buffer offset and populates the CRB data window.
>
> For this to work, the linux guest tpm driver will also have to
> a) probe if CRB chunking is supported
> b) send data in chunks if the command length exceeds the chunk size.
> c) receive data in chunks by sending a nextChunk signal and accumulate.
> > At present, VM live migration during a tpm command execution in chunks
> is not supported. We intend to add support for TIS interface in
.. and also VM suspend/resume. Do you intend to support these? The
problem will be state incompatibility with previous QEMU versions then
since we now need to save the additional (bytes in the new) byte
buffer(s) and possibly the new control registers' contents.
Thanks for also thinking of the TIS support. I guess we will have the
same issue there then with state incompatibilities even though with the
TIS it should just be a matter of just using a bigger buffer.
Theoretically we could write a 4096 byte buffer to the state if up to
4kb are held there and switch to a .version_id = 1 state for bigger
buffer size, but then we could migrate to an older version of QEMU and
find that it cannot do PQC transactions (anymore) due to older version
of TIS there (I guess migration between major versions of QEMU is
supported with all kinds of machines). Also, nothing will prevent one
from running a PQC-enabled swtpm+libtpms with an older version of QEMU
but one will find out if transactions don't succeed (assuming Linux TPM
driver can handle it) because the QEMU TIS buffer is too small.
> the future.
>
> The included test demonstrates functional correctness for standard
> buffer sizes. However, validation of PQC-sized payloads was performed
> via manual buffer-size overrides.
> > [1]
https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
>
> Arun Menon (5):
> hw/tpm: Add TPM CRB chunking fields
> hw/tpm: Refactor CRB_CTRL_START register access
> hw/tpm: Add internal buffer state for chunking
> hw/tpm: Implement TPM CRB chunking logic
> test/qtest: Add test for tpm crb chunking
>
> hw/tpm/tpm_crb.c | 167 +++++++++++++++++++++++++++----
> include/hw/acpi/tpm.h | 5 +-
> tests/qtest/tpm-crb-swtpm-test.c | 10 ++
> tests/qtest/tpm-util.c | 106 +++++++++++++++++---
> tests/qtest/tpm-util.h | 5 +
> 5 files changed, 256 insertions(+), 37 deletions(-)
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 1/5] hw/tpm: Add TPM CRB chunking fields
2026-03-12 4:16 ` [RFC 1/5] hw/tpm: Add TPM CRB chunking fields Arun Menon
@ 2026-03-13 12:28 ` Stefan Berger
0 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2026-03-13 12:28 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov
On 3/12/26 12:16 AM, Arun Menon wrote:
> - 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>
> ---
> 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)
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access
2026-03-12 4:16 ` [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
@ 2026-03-13 12:33 ` Stefan Berger
0 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2026-03-13 12:33 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov
On 3/12/26 12:16 AM, Arun Menon wrote:
> Replace manual bitwise operations with ARRAY_FIELD_DP32 macros
> No functional changes.
>
> Signed-off-by: Arun Menon <armenon@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 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 */
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic
2026-03-12 4:16 ` [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic Arun Menon
@ 2026-03-13 17:55 ` Stefan Berger
0 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2026-03-13 17:55 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov
On 3/12/26 12:16 AM, Arun Menon wrote:
> - 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>
> ---
> hw/tpm/tpm_crb.c | 138 +++++++++++++++++++++++++++++++++++++++++------
> 1 file changed, 122 insertions(+), 16 deletions(-)
>
> diff --git a/hw/tpm/tpm_crb.c b/hw/tpm/tpm_crb.c
> index 5ea1a4a970..845f9c6c9f 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,58 @@ 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)
A short description of what is being appended from where would be helpful.
> +{
> + 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)
Also a short description for this function would be helpful.
> +{
> + 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 +208,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 +289,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 +395,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);
With the two short description added:
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC
2026-03-12 14:36 ` [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Stefan Berger
@ 2026-03-13 19:35 ` Arun Menon
2026-03-13 21:06 ` Stefan Berger
0 siblings, 1 reply; 14+ messages in thread
From: Arun Menon @ 2026-03-13 19:35 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Stefan Berger, Ani Sinha, Paolo Bonzini,
marcandre.lureau, Fabiano Rosas, Laurent Vivier,
Michael S. Tsirkin, Igor Mammedov
Hi Stefan,
Thank you for taking a look into this.
On Thu, Mar 12, 2026 at 10:36:29AM -0400, Stefan Berger wrote:
>
>
> On 3/12/26 12:16 AM, Arun Menon wrote:
> > The move to Post Quantum Cryptography (PQC) changes how we manage
> > memory buffers. Unlike classic crypto algorithms like RSA or ECC which
> > used small keys and signatures, PQC algorithms require larger buffers.
> >
> > The new version of TCG TPM v185 (currently under review [1]) supports
> > sending data/commands in chunks for the CRB (Command Response Buffer)
> > interface. This is in line with the initiative to support PQC algorithms.
> >
> > This series implements the logic to send and receive data from the
> > linux guest to the TPM backend in chunks, thereby allowing the
> > guest to send larger data buffers. We introduce 2 new control registers
> > called nextChunk and crbRspRetry that will control the START. We also
> > add the CRB Interface Identifier called CapCRBChunk that is set to 1
> > indicating that the device supports chunking. The default maximum
> > chunk/buffer size is 3968 (4096 - 128) bytes.
> >
> > During a send operation, the guest driver places data in the CRB buffer
> > and signals nextChunk for each segment until the final chunk is reached.
> > Upon receiving the START signal, QEMU appends the final chunk to its
> > internal buffer and dispatches the complete command to the TPM backend.
> >
> > For responses, the backend's output is buffered. The guest consumes the
> > first chunk once the START bit is cleared. Subsequent chunks are
> > retrieved by the guest toggling the nextChunk bit, which advances the
> > internal buffer offset and populates the CRB data window.
> >
> > For this to work, the linux guest tpm driver will also have to
> > a) probe if CRB chunking is supported
> > b) send data in chunks if the command length exceeds the chunk size.
> > c) receive data in chunks by sending a nextChunk signal and accumulate.
> > > At present, VM live migration during a tpm command execution in chunks
> > is not supported. We intend to add support for TIS interface in
>
> .. and also VM suspend/resume. Do you intend to support these? The problem
> will be state incompatibility with previous QEMU versions then since we now
> need to save the additional (bytes in the new) byte buffer(s) and possibly
> the new control registers' contents.
Yes I would like to support suspend/resume along with migration.
And yes, I will start by saving those buffer information in the VMstate
subsection.
Also, I am working on the linux driver part. The logic is more
or less the same; 2 buffers, accumulating data, and send/receive based
on the new register bit.
>
> Thanks for also thinking of the TIS support. I guess we will have the same
> issue there then with state incompatibilities even though with the TIS it
> should just be a matter of just using a bigger buffer. Theoretically we
> could write a 4096 byte buffer to the state if up to 4kb are held there and
> switch to a .version_id = 1 state for bigger buffer size, but then we could
> migrate to an older version of QEMU and find that it cannot do PQC
> transactions (anymore) due to older version of TIS there (I guess migration
> between major versions of QEMU is supported with all kinds of machines).
> Also, nothing will prevent one from running a PQC-enabled swtpm+libtpms with
> an older version of QEMU but one will find out if transactions don't succeed
> (assuming Linux TPM driver can handle it) because the QEMU TIS buffer is too
> small.
Yes the same issue will come with TIS. Although changing the buffer size
TPM_TIS_BUFFER_MAX to 8192 (for now) would be a one liner change, but
handling the state, for migration and other cases will be tricky like
you mentioned for both crb and tis. If I understand correctly,
We need to know whether
a) swtpm OR the TPM backend supports chunking
b) does the underlying machine type of qemu suport chunking.
I am not sure if swtpm can set a capability bit if chunking is
supported. If we can do that, then QEMU can probe the backend and get
that information. But that will not be sufficient. To decide whether
chunking is actually allowed based on the underlying machine type we
might have to add a prop bool for the specific machine type.
If both of them (a and b) are true, only the linux probing will return
true for chunking. Thus linux will not send the huge buffers in the
first place, or even try to copy data in the subsections during
migration with the .needed function.
Please correct me if I am wrong.
>
> > the future.
> >
> > The included test demonstrates functional correctness for standard
> > buffer sizes. However, validation of PQC-sized payloads was performed
> > via manual buffer-size overrides.
> > > [1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
> >
> > Arun Menon (5):
> > hw/tpm: Add TPM CRB chunking fields
> > hw/tpm: Refactor CRB_CTRL_START register access
> > hw/tpm: Add internal buffer state for chunking
> > hw/tpm: Implement TPM CRB chunking logic
> > test/qtest: Add test for tpm crb chunking
> >
> > hw/tpm/tpm_crb.c | 167 +++++++++++++++++++++++++++----
> > include/hw/acpi/tpm.h | 5 +-
> > tests/qtest/tpm-crb-swtpm-test.c | 10 ++
> > tests/qtest/tpm-util.c | 106 +++++++++++++++++---
> > tests/qtest/tpm-util.h | 5 +
> > 5 files changed, 256 insertions(+), 37 deletions(-)
> >
>
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 5/5] test/qtest: Add test for tpm crb chunking
2026-03-12 4:16 ` [RFC 5/5] test/qtest: Add test for tpm crb chunking Arun Menon
@ 2026-03-13 20:30 ` Stefan Berger
0 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2026-03-13 20:30 UTC (permalink / raw)
To: Arun Menon, qemu-devel
Cc: Stefan Berger, Ani Sinha, Paolo Bonzini, marcandre.lureau,
Fabiano Rosas, Laurent Vivier, Michael S. Tsirkin, Igor Mammedov
On 3/12/26 12:16 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>
> ---
> 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);
This test would not exercise the chunk functionality unless one modifies
CRB_CTRL_CMD_SIZE and rebuilds. And for this particular test case its
value would have to be rather small since only rather small commands are
sent, which in turn could affect other test cases that do not do chunked
transfers.
Future versions of swtpm will return 8192 when queried for the buffer
size (tpm_ioctl.h PTM_SET_BUFFERSIZE with buffersize=0) and we could
have a better test case then, like creating an ML-DSA-87 key and loading
it back into the TPM. Hashing of large inputs unfortunately does not
work... only up to 1kb.
> +}
> +
> 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..0a861a9e69 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)
you can remove '(' and ')'
> + : 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,
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC
2026-03-13 19:35 ` Arun Menon
@ 2026-03-13 21:06 ` Stefan Berger
2026-03-19 14:14 ` Arun Menon
0 siblings, 1 reply; 14+ messages in thread
From: Stefan Berger @ 2026-03-13 21:06 UTC (permalink / raw)
To: Arun Menon
Cc: qemu-devel, Stefan Berger, Ani Sinha, Paolo Bonzini,
marcandre.lureau, Fabiano Rosas, Laurent Vivier,
Michael S. Tsirkin, Igor Mammedov
On 3/13/26 3:35 PM, Arun Menon wrote:
> Hi Stefan,
> Thank you for taking a look into this.
>
> On Thu, Mar 12, 2026 at 10:36:29AM -0400, Stefan Berger wrote:
>>
>>
>> On 3/12/26 12:16 AM, Arun Menon wrote:
>>> The move to Post Quantum Cryptography (PQC) changes how we manage
>>> memory buffers. Unlike classic crypto algorithms like RSA or ECC which
>>> used small keys and signatures, PQC algorithms require larger buffers.
>>>
>>> The new version of TCG TPM v185 (currently under review [1]) supports
>>> sending data/commands in chunks for the CRB (Command Response Buffer)
>>> interface. This is in line with the initiative to support PQC algorithms.
>>>
>>> This series implements the logic to send and receive data from the
>>> linux guest to the TPM backend in chunks, thereby allowing the
>>> guest to send larger data buffers. We introduce 2 new control registers
>>> called nextChunk and crbRspRetry that will control the START. We also
>>> add the CRB Interface Identifier called CapCRBChunk that is set to 1
>>> indicating that the device supports chunking. The default maximum
>>> chunk/buffer size is 3968 (4096 - 128) bytes.
>>>
>>> During a send operation, the guest driver places data in the CRB buffer
>>> and signals nextChunk for each segment until the final chunk is reached.
>>> Upon receiving the START signal, QEMU appends the final chunk to its
>>> internal buffer and dispatches the complete command to the TPM backend.
>>>
>>> For responses, the backend's output is buffered. The guest consumes the
>>> first chunk once the START bit is cleared. Subsequent chunks are
>>> retrieved by the guest toggling the nextChunk bit, which advances the
>>> internal buffer offset and populates the CRB data window.
>>>
>>> For this to work, the linux guest tpm driver will also have to
>>> a) probe if CRB chunking is supported
>>> b) send data in chunks if the command length exceeds the chunk size.
>>> c) receive data in chunks by sending a nextChunk signal and accumulate.
>>>> At present, VM live migration during a tpm command execution in chunks
>>> is not supported. We intend to add support for TIS interface in
>>
>> .. and also VM suspend/resume. Do you intend to support these? The problem
>> will be state incompatibility with previous QEMU versions then since we now
>> need to save the additional (bytes in the new) byte buffer(s) and possibly
>> the new control registers' contents.
>
> Yes I would like to support suspend/resume along with migration.
> And yes, I will start by saving those buffer information in the VMstate
> subsection.
If we bump up the version from state v0 to v1, can v0 capable devices
still read the state and discard data that are for v1?
I am not sure whether the following is possible - the is maintaining
backwards compatibility as much as possible:
- if the CRB OS driver never touched the chunking during the
transaction, write v0 state
- if the CRB OS driver touched the chunking feature during the
transaction, write v1 state
I was looking whether we can still use qemu_put_byte() etc. but was
missing the .save interface from DeviceClass for example.
>
> Also, I am working on the linux driver part. The logic is more
> or less the same; 2 buffers, accumulating data, and send/receive based
> on the new register bit.
>
>>
>> Thanks for also thinking of the TIS support. I guess we will have the same
>> issue there then with state incompatibilities even though with the TIS it
>> should just be a matter of just using a bigger buffer. Theoretically we
>> could write a 4096 byte buffer to the state if up to 4kb are held there and
>> switch to a .version_id = 1 state for bigger buffer size, but then we could
>> migrate to an older version of QEMU and find that it cannot do PQC
>> transactions (anymore) due to older version of TIS there (I guess migration
>> between major versions of QEMU is supported with all kinds of machines).
>> Also, nothing will prevent one from running a PQC-enabled swtpm+libtpms with
>> an older version of QEMU but one will find out if transactions don't succeed
>> (assuming Linux TPM driver can handle it) because the QEMU TIS buffer is too
>> small.
>
> Yes the same issue will come with TIS. Although changing the buffer size
> TPM_TIS_BUFFER_MAX to 8192 (for now) would be a one liner change, but
> handling the state, for migration and other cases will be tricky like
> you mentioned for both crb and tis. If I understand correctly,
> We need to know whether
> a) swtpm OR the TPM backend supports chunking
I am collecting these buffer-related issue here considering combinations
of different versions of libtpms, swtpm, and QEMU (not considering Linux
driver). The table is probably the best for now to see the problematic
combinations on the lower layers:
https://github.com/stefanberger/swtpm/issues/1076
> b) does the underlying machine type of qemu suport chunking.
>
> I am not sure if swtpm can set a capability bit if chunking is
> supported. If we can do that, then QEMU can probe the backend and get
swtpm (v0.11 and via backport also v0.10.x) will simply work with a 8kb
buffer once libtpms (v0.11) tells it that it can support up to 8 kb buffers.
> that information. But that will not be sufficient. To decide whether
> chunking is actually allowed based on the underlying machine type we
> might have to add a prop bool for the specific machine type.
>
Are you referring the CRB capability bit that advertises the chunking
capability? I think a good sign that chunking should be supported by CRB
(set the chunking-supported bit CapCRBChunk) is that the buffer queried
for from swtpm indicates > 4kb (s->be_buffer_size =
tpm_backend_get_buffer_size(s->tpmbe) ). If the buffer size is still 4kb
it probably should not advertise the chunking capability -- hadn't
thought of this while reviewing the patches, but ... you may want to add
this in the code.
> If both of them (a and b) are true, only the linux probing will return
> true for chunking. Thus linux will not send the huge buffers in the
> first place, or even try to copy data in the subsections during
> migration with the .needed function.
>
> Please correct me if I am wrong.
I am not sure about all the details yet, either.
>
>
>>
>>> the future.
>>>
>>> The included test demonstrates functional correctness for standard
>>> buffer sizes. However, validation of PQC-sized payloads was performed
>>> via manual buffer-size overrides.
>>>> [1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
>>>
>>> Arun Menon (5):
>>> hw/tpm: Add TPM CRB chunking fields
>>> hw/tpm: Refactor CRB_CTRL_START register access
>>> hw/tpm: Add internal buffer state for chunking
>>> hw/tpm: Implement TPM CRB chunking logic
>>> test/qtest: Add test for tpm crb chunking
>>>
>>> hw/tpm/tpm_crb.c | 167 +++++++++++++++++++++++++++----
>>> include/hw/acpi/tpm.h | 5 +-
>>> tests/qtest/tpm-crb-swtpm-test.c | 10 ++
>>> tests/qtest/tpm-util.c | 106 +++++++++++++++++---
>>> tests/qtest/tpm-util.h | 5 +
>>> 5 files changed, 256 insertions(+), 37 deletions(-)
>>>
>>
>>
>
> Regards,
> Arun Menon
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC
2026-03-13 21:06 ` Stefan Berger
@ 2026-03-19 14:14 ` Arun Menon
0 siblings, 0 replies; 14+ messages in thread
From: Arun Menon @ 2026-03-19 14:14 UTC (permalink / raw)
To: Stefan Berger
Cc: qemu-devel, Stefan Berger, Ani Sinha, Paolo Bonzini,
marcandre.lureau, Fabiano Rosas, Laurent Vivier,
Michael S. Tsirkin, Igor Mammedov, Peter Xu
Hi Stefan,
Sorry for the late response.
On Fri, Mar 13, 2026 at 05:06:16PM -0400, Stefan Berger wrote:
>
>
> On 3/13/26 3:35 PM, Arun Menon wrote:
> > Hi Stefan,
> > Thank you for taking a look into this.
> >
> > On Thu, Mar 12, 2026 at 10:36:29AM -0400, Stefan Berger wrote:
> > >
> > >
> > > On 3/12/26 12:16 AM, Arun Menon wrote:
> > > > The move to Post Quantum Cryptography (PQC) changes how we manage
> > > > memory buffers. Unlike classic crypto algorithms like RSA or ECC which
> > > > used small keys and signatures, PQC algorithms require larger buffers.
> > > >
> > > > The new version of TCG TPM v185 (currently under review [1]) supports
> > > > sending data/commands in chunks for the CRB (Command Response Buffer)
> > > > interface. This is in line with the initiative to support PQC algorithms.
> > > >
> > > > This series implements the logic to send and receive data from the
> > > > linux guest to the TPM backend in chunks, thereby allowing the
> > > > guest to send larger data buffers. We introduce 2 new control registers
> > > > called nextChunk and crbRspRetry that will control the START. We also
> > > > add the CRB Interface Identifier called CapCRBChunk that is set to 1
> > > > indicating that the device supports chunking. The default maximum
> > > > chunk/buffer size is 3968 (4096 - 128) bytes.
> > > >
> > > > During a send operation, the guest driver places data in the CRB buffer
> > > > and signals nextChunk for each segment until the final chunk is reached.
> > > > Upon receiving the START signal, QEMU appends the final chunk to its
> > > > internal buffer and dispatches the complete command to the TPM backend.
> > > >
> > > > For responses, the backend's output is buffered. The guest consumes the
> > > > first chunk once the START bit is cleared. Subsequent chunks are
> > > > retrieved by the guest toggling the nextChunk bit, which advances the
> > > > internal buffer offset and populates the CRB data window.
> > > >
> > > > For this to work, the linux guest tpm driver will also have to
> > > > a) probe if CRB chunking is supported
> > > > b) send data in chunks if the command length exceeds the chunk size.
> > > > c) receive data in chunks by sending a nextChunk signal and accumulate.
> > > > > At present, VM live migration during a tpm command execution in chunks
> > > > is not supported. We intend to add support for TIS interface in
> > >
> > > .. and also VM suspend/resume. Do you intend to support these? The problem
> > > will be state incompatibility with previous QEMU versions then since we now
> > > need to save the additional (bytes in the new) byte buffer(s) and possibly
> > > the new control registers' contents.
> >
> > Yes I would like to support suspend/resume along with migration.
> > And yes, I will start by saving those buffer information in the VMstate
> > subsection.
>
> If we bump up the version from state v0 to v1, can v0 capable devices still
> read the state and discard data that are for v1?
>
> I am not sure whether the following is possible - the is maintaining
> backwards compatibility as much as possible:
> - if the CRB OS driver never touched the chunking during the transaction,
> write v0 state
> - if the CRB OS driver touched the chunking feature during the transaction,
> write v1 state
>
> I was looking whether we can still use qemu_put_byte() etc. but was missing
> the .save interface from DeviceClass for example.
In v2 of this series, I have added support for migration. I was not able
to test it thoroughly because of the nature of the case we are aiming to
incorporate. I think backwards compatibility is maintained by adding an
entry to the hw_compat array. I am no expert, therefore I feel its best
to ask someone who understands migration well. @peterx@redhat.com ?
https://www.qemu.org/docs/master/devel/migration/compatibility.html#how-backwards-compatibility-works
If the user starts a new qemu instance with an older machine type, then
the new feature will not be enabled at all due to this flag, allowing the
user to migrate to an older qemu binary while keeping the same
machine type. The subsection will not be sent at all.
>
> >
> > Also, I am working on the linux driver part. The logic is more
> > or less the same; 2 buffers, accumulating data, and send/receive based
> > on the new register bit.
> >
> > >
> > > Thanks for also thinking of the TIS support. I guess we will have the same
> > > issue there then with state incompatibilities even though with the TIS it
> > > should just be a matter of just using a bigger buffer. Theoretically we
> > > could write a 4096 byte buffer to the state if up to 4kb are held there and
> > > switch to a .version_id = 1 state for bigger buffer size, but then we could
> > > migrate to an older version of QEMU and find that it cannot do PQC
> > > transactions (anymore) due to older version of TIS there (I guess migration
> > > between major versions of QEMU is supported with all kinds of machines).
> > > Also, nothing will prevent one from running a PQC-enabled swtpm+libtpms with
> > > an older version of QEMU but one will find out if transactions don't succeed
> > > (assuming Linux TPM driver can handle it) because the QEMU TIS buffer is too
> > > small.
> >
> > Yes the same issue will come with TIS. Although changing the buffer size
> > TPM_TIS_BUFFER_MAX to 8192 (for now) would be a one liner change, but
> > handling the state, for migration and other cases will be tricky like
> > you mentioned for both crb and tis. If I understand correctly,
> > We need to know whether
> > a) swtpm OR the TPM backend supports chunking
>
> I am collecting these buffer-related issue here considering combinations of
> different versions of libtpms, swtpm, and QEMU (not considering Linux
> driver). The table is probably the best for now to see the problematic
> combinations on the lower layers:
>
> https://github.com/stefanberger/swtpm/issues/1076
Thank you for this nice list.
>
> > b) does the underlying machine type of qemu suport chunking.
> >
> > I am not sure if swtpm can set a capability bit if chunking is
> > supported. If we can do that, then QEMU can probe the backend and get
>
> swtpm (v0.11 and via backport also v0.10.x) will simply work with a 8kb
> buffer once libtpms (v0.11) tells it that it can support up to 8 kb buffers.
>
> > that information. But that will not be sufficient. To decide whether
> > chunking is actually allowed based on the underlying machine type we
> > might have to add a prop bool for the specific machine type.
> >
>
> Are you referring the CRB capability bit that advertises the chunking
> capability? I think a good sign that chunking should be supported by CRB
> (set the chunking-supported bit CapCRBChunk) is that the buffer queried for
> from swtpm indicates > 4kb (s->be_buffer_size =
> tpm_backend_get_buffer_size(s->tpmbe) ). If the buffer size is still 4kb it
> probably should not advertise the chunking capability -- hadn't thought of
> this while reviewing the patches, but ... you may want to add this in the
> code.
>
At the moment the new qemu binary simply sets the capability to be
true by default. I missed this in v2; I shall add it to tpm_crb_reset()
or tpm_crb_realize() when i post non RFC version.
> > If both of them (a and b) are true, only the linux probing will return
> > true for chunking. Thus linux will not send the huge buffers in the
> > first place, or even try to copy data in the subsections during
> > migration with the .needed function.
> >
> > Please correct me if I am wrong.
>
> I am not sure about all the details yet, either.
>
> >
> >
> > >
> > > > the future.
> > > >
> > > > The included test demonstrates functional correctness for standard
> > > > buffer sizes. However, validation of PQC-sized payloads was performed
> > > > via manual buffer-size overrides.
> > > > > [1] https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p07_rc1_121225.pdf
> > > >
> > > > Arun Menon (5):
> > > > hw/tpm: Add TPM CRB chunking fields
> > > > hw/tpm: Refactor CRB_CTRL_START register access
> > > > hw/tpm: Add internal buffer state for chunking
> > > > hw/tpm: Implement TPM CRB chunking logic
> > > > test/qtest: Add test for tpm crb chunking
> > > >
> > > > hw/tpm/tpm_crb.c | 167 +++++++++++++++++++++++++++----
> > > > include/hw/acpi/tpm.h | 5 +-
> > > > tests/qtest/tpm-crb-swtpm-test.c | 10 ++
> > > > tests/qtest/tpm-util.c | 106 +++++++++++++++++---
> > > > tests/qtest/tpm-util.h | 5 +
> > > > 5 files changed, 256 insertions(+), 37 deletions(-)
> > > >
> > >
> > >
> >
> > Regards,
> > Arun Menon
> >
>
>
Regards,
Arun Menon
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-03-19 14:15 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-12 4:16 [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Arun Menon
2026-03-12 4:16 ` [RFC 1/5] hw/tpm: Add TPM CRB chunking fields Arun Menon
2026-03-13 12:28 ` Stefan Berger
2026-03-12 4:16 ` [RFC 2/5] hw/tpm: Refactor CRB_CTRL_START register access Arun Menon
2026-03-13 12:33 ` Stefan Berger
2026-03-12 4:16 ` [RFC 3/5] hw/tpm: Add internal buffer state for chunking Arun Menon
2026-03-12 4:16 ` [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic Arun Menon
2026-03-13 17:55 ` Stefan Berger
2026-03-12 4:16 ` [RFC 5/5] test/qtest: Add test for tpm crb chunking Arun Menon
2026-03-13 20:30 ` Stefan Berger
2026-03-12 14:36 ` [RFC 0/5] hw/tpm: CRB chunking capability to handle PQC Stefan Berger
2026-03-13 19:35 ` Arun Menon
2026-03-13 21:06 ` Stefan Berger
2026-03-19 14:14 ` Arun Menon
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox