- 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 <[email protected]> --- 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
