- 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
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>
© 2016 - 2026 Red Hat, Inc.