[RFC 4/5] hw/tpm: Implement TPM CRB chunking logic

Arun Menon posted 5 patches 3 weeks, 5 days ago
Maintainers: Stefan Berger <stefanb@linux.vnet.ibm.com>, "Michael S. Tsirkin" <mst@redhat.com>, Igor Mammedov <imammedo@redhat.com>, Ani Sinha <anisinha@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
[RFC 4/5] hw/tpm: Implement TPM CRB chunking logic
Posted by Arun Menon 3 weeks, 5 days ago
- 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
Re: [RFC 4/5] hw/tpm: Implement TPM CRB chunking logic
Posted by Stefan Berger 3 weeks, 4 days ago

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>