@samitouri / QOSamiQemu / commits / 2a660ad67d

hw/tpm: Implement TPM CRB chunking logic

- 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_Pub.pdf Signed-off-by: Arun Menon <armenon@redhat.com> Reviewed-by: Stefan Berger <stefanb@linux.ibm.com> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Link: https://lore.kernel.org/qemu-devel/20260506075813.120781-5-armenon@redhat.com Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>

Arun Menon committed May 6, 2026 at 13:28 UTC 2a660ad67d1566c06fcf75c413c6e10a02157124
1 file changed +130 -13
hw/tpm/tpm_crb.c
+130 -13
@@ -17,6 +17,7 @@
17 #include "qemu/osdep.h"
18
19 #include "qemu/module.h"
20 +#include "qemu/error-report.h"
21 #include "qapi/error.h"
22 #include "system/address-spaces.h"
23 #include "hw/core/qdev-properties.h"
@@ -66,6 +67,7 @@ DECLARE_INSTANCE_CHECKER(CRBState, CRB,
67 #define CRB_INTF_CAP_CRB_CHUNK 0b1
68
69 #define CRB_CTRL_CMD_SIZE (TPM_CRB_ADDR_SIZE - A_CRB_DATA_BUFFER)
70 +#define TPM_HEADER_SIZE 10
71
72 enum crb_loc_ctrl {
73 CRB_LOC_CTRL_REQUEST_ACCESS = BIT(0),
@@ -81,6 +83,8 @@ enum crb_ctrl_req {
83
84 enum crb_start {
85 CRB_START_INVOKE = BIT(0),
86 + CRB_START_RSP_RETRY = BIT(1),
87 + CRB_START_NEXT_CHUNK = BIT(2),
88 };
89
90 enum crb_cancel {
@@ -123,6 +127,69 @@ static uint8_t tpm_crb_get_active_locty(CRBState *s)
127 return ARRAY_FIELD_EX32(s->regs, CRB_LOC_STATE, activeLocality);
128 }
129
130 +static bool tpm_crb_append_command_request(CRBState *s)
131 +{
132 + /*
133 + * The linux guest writes the TPM command to the MMIO region in chunks.
134 + * This function appends a chunk from the MMIO region to internal
135 + * command_buffer.
136 + */
137 + void *mem = memory_region_get_ram_ptr(&s->cmdmem);
138 + uint32_t to_copy = 0;
139 + uint32_t total_request_size = 0;
140 +
141 + /*
142 + * The initial call extracts the total TPM command size
143 + * from its header. For the subsequent calls, the data already
144 + * appended in the command_buffer is used to calculate the total
145 + * size, as its header stays the same.
146 + */
147 + if (s->command_buffer->len == 0) {
148 + total_request_size = tpm_cmd_get_size(mem);
149 + if (total_request_size < TPM_HEADER_SIZE) {
150 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS, tpmSts, 1);
151 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 0);
152 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
153 + tpm_crb_clear_internal_buffers(s);
154 + error_report("Command size %" PRIu32 " less than "
155 + "TPM header size %" PRIu32,
156 + total_request_size, (uint32_t)TPM_HEADER_SIZE);
157 + return false;
158 + }
159 + } else {
160 + total_request_size = tpm_cmd_get_size(s->command_buffer->data);
161 + }
162 + total_request_size = MIN(total_request_size, s->be_buffer_size);
163 +
164 + if (total_request_size > s->command_buffer->len) {
165 + uint32_t remaining = total_request_size - s->command_buffer->len;
166 + to_copy = MIN(remaining, CRB_CTRL_CMD_SIZE);
167 + g_byte_array_append(s->command_buffer, (guint8 *)mem, to_copy);
168 + }
169 + return true;
170 +}
171 +
172 +static void tpm_crb_fill_command_response(CRBState *s)
173 +{
174 + /*
175 + * Response from the tpm backend will be stored in the internal
176 + * response_buffer. This function will serve that accumulated response
177 + * to the linux guest in chunks by writing it back to MMIO region.
178 + */
179 + void *mem = memory_region_get_ram_ptr(&s->cmdmem);
180 + uint32_t remaining = s->response_buffer->len - s->response_offset;
181 + uint32_t to_copy = MIN(CRB_CTRL_CMD_SIZE, remaining);
182 +
183 + memcpy(mem, s->response_buffer->data + s->response_offset, to_copy);
184 +
185 + if (to_copy < CRB_CTRL_CMD_SIZE) {
186 + memset((guint8 *)mem + to_copy, 0, CRB_CTRL_CMD_SIZE - to_copy);
187 + }
188 +
189 + s->response_offset += to_copy;
190 + memory_region_set_dirty(&s->cmdmem, 0, CRB_CTRL_CMD_SIZE);
191 +}
192 +
193 static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
194 uint64_t val, unsigned size)
195 {
@@ -153,20 +220,58 @@ static void tpm_crb_mmio_write(void *opaque, hwaddr addr,
220 }
221 break;
222 case A_CRB_CTRL_START:
156 - if (val == CRB_START_INVOKE &&
157 - !(s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) &&
158 - tpm_crb_get_active_locty(s) == locty) {
159 - void *mem = memory_region_get_ram_ptr(&s->cmdmem);
160 -
223 + if (tpm_crb_get_active_locty(s) != locty) {
224 + break;
225 + }
226 + if (s->regs[R_CRB_CTRL_START] & CRB_START_INVOKE) {
227 + /*
228 + * Backend TPM is busy processing a request.
229 + */
230 + break;
231 + }
232 + if (val & CRB_START_INVOKE) {
233 + if (!tpm_crb_append_command_request(s)) {
234 + break;
235 + }
236 ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, Start, 1);
237 + g_byte_array_set_size(s->response_buffer, s->be_buffer_size);
238 s->cmd = (TPMBackendCmd) {
163 - .in = mem,
164 - .in_len = MIN(tpm_cmd_get_size(mem), s->be_buffer_size),
165 - .out = mem,
166 - .out_len = s->be_buffer_size,
239 + .in = s->command_buffer->data,
240 + .in_len = s->command_buffer->len,
241 + .out = s->response_buffer->data,
242 + .out_len = s->response_buffer->len,
243 };
168 -
244 tpm_backend_deliver_request(s->tpmbe, &s->cmd);
245 + } else if (val & CRB_START_NEXT_CHUNK) {
246 + if (!s->cap_chunk) {
247 + break;
248 + }
249 + /*
250 + * nextChunk is used both while sending and receiving data.
251 + * To distinguish between the two, response_buffer is checked.
252 + * If it does not have data, then that means we have not yet
253 + * sent the command to the tpm backend, and therefore call
254 + * tpm_crb_append_command_request().
255 + */
256 + if (s->response_buffer->len > 0 &&
257 + s->response_offset < s->response_buffer->len) {
258 + tpm_crb_fill_command_response(s);
259 + } else {
260 + if (!tpm_crb_append_command_request(s)) {
261 + break;
262 + }
263 + }
264 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
265 + } else if (val & CRB_START_RSP_RETRY) {
266 + if (!s->cap_chunk) {
267 + break;
268 + }
269 + if (s->response_buffer->len > 0) {
270 + s->response_offset = 0;
271 + tpm_crb_fill_command_response(s);
272 + }
273 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, crbRspRetry, 0);
274 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
275 }
276 break;
277 case A_CRB_LOC_CTRL:
@@ -211,8 +316,21 @@ static void tpm_crb_request_completed(TPMIf *ti, int ret)
316 if (ret != 0) {
317 ARRAY_FIELD_DP32(s->regs, CRB_CTRL_STS,
318 tpmSts, 1); /* fatal error */
319 + tpm_crb_clear_internal_buffers(s);
320 + } else {
321 + uint32_t actual_resp_size = tpm_cmd_get_size(s->response_buffer->data);
322 + uint32_t total_resp_size = MIN(actual_resp_size, s->be_buffer_size);
323 + g_byte_array_set_size(s->response_buffer, total_resp_size);
324 + s->response_offset = 0;
325 }
215 - memory_region_set_dirty(&s->cmdmem, 0, CRB_CTRL_CMD_SIZE);
326 + /*
327 + * Send the first chunk. Subsequent chunks will be sent
328 + * on receiving nextChunk from the guest
329 + */
330 + tpm_crb_fill_command_response(s);
331 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, nextChunk, 0);
332 + ARRAY_FIELD_DP32(s->regs, CRB_CTRL_START, crbRspRetry, 0);
333 + g_byte_array_set_size(s->command_buffer, 0);
334 }
335
336 static enum TPMVersion tpm_crb_get_version(TPMIf *ti)
@@ -287,8 +405,7 @@ static void tpm_crb_reset(void *dev)
405 s->regs[R_CRB_CTRL_RSP_SIZE] = CRB_CTRL_CMD_SIZE;
406 s->regs[R_CRB_CTRL_RSP_ADDR] = TPM_CRB_ADDR_BASE + A_CRB_DATA_BUFFER;
407
290 - s->be_buffer_size = MIN(tpm_backend_get_buffer_size(s->tpmbe),
291 - CRB_CTRL_CMD_SIZE);
408 + s->be_buffer_size = tpm_backend_get_buffer_size(s->tpmbe);
409
410 if (tpm_backend_startup_tpm(s->tpmbe, s->be_buffer_size) < 0) {
411 exit(1);