]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/bhyve/tpm_intf_crb.c
Merge llvm-project release/17.x llvmorg-17.0.0-rc4-10-g0176e8729ea4
[FreeBSD/FreeBSD.git] / usr.sbin / bhyve / tpm_intf_crb.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5  * Author: Corvin Köhne <c.koehne@beckhoff.com>
6  */
7
8 #include <sys/types.h>
9 #include <sys/param.h>
10 #include <sys/linker_set.h>
11
12 #include <machine/vmm.h>
13
14 #include <assert.h>
15 #include <err.h>
16 #include <errno.h>
17 #include <pthread.h>
18 #include <pthread_np.h>
19 #include <stddef.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <vmmapi.h>
23
24 #include "basl.h"
25 #include "config.h"
26 #include "mem.h"
27 #include "qemu_fwcfg.h"
28 #include "tpm_device.h"
29 #include "tpm_intf.h"
30
31 #define TPM_CRB_ADDRESS 0xFED40000
32 #define TPM_CRB_REGS_SIZE 0x1000
33
34 #define TPM_CRB_CONTROL_AREA_ADDRESS \
35         (TPM_CRB_ADDRESS + offsetof(struct tpm_crb_regs, ctrl_req))
36 #define TPM_CRB_CONTROL_AREA_SIZE TPM_CRB_REGS_SIZE
37
38 #define TPM_CRB_DATA_BUFFER_ADDRESS \
39         (TPM_CRB_ADDRESS + offsetof(struct tpm_crb_regs, data_buffer))
40 #define TPM_CRB_DATA_BUFFER_SIZE 0xF80
41
42 #define TPM_CRB_LOCALITIES_MAX 5
43
44 #define TPM_CRB_LOG_AREA_MINIMUM_SIZE (64 * 1024)
45
46 #define TPM_CRB_LOG_AREA_FWCFG_NAME "etc/tpm/log"
47
48 #define TPM_CRB_INTF_NAME "crb"
49
50 struct tpm_crb_regs {
51         union tpm_crb_reg_loc_state {
52                 struct {
53                         uint32_t tpm_established : 1;
54                         uint32_t loc_assigned : 1;
55                         uint32_t active_locality : 3;
56                         uint32_t _reserved : 2;
57                         uint32_t tpm_req_valid_sts : 1;
58                 };
59                 uint32_t val;
60         } loc_state;           /* 0h */
61         uint8_t _reserved1[4]; /* 4h */
62         union tpm_crb_reg_loc_ctrl {
63                 struct {
64                         uint32_t request_access : 1;
65                         uint32_t relinquish : 1;
66                         uint32_t seize : 1;
67                         uint32_t reset_establishment_bit : 1;
68                 };
69                 uint32_t val;
70         } loc_ctrl; /* 8h */
71         union tpm_crb_reg_loc_sts {
72                 struct {
73                         uint32_t granted : 1;
74                         uint32_t been_seized : 1;
75                 };
76                 uint32_t val;
77         } loc_sts;                /* Ch */
78         uint8_t _reserved2[0x20]; /* 10h */
79         union tpm_crb_reg_intf_id {
80                 struct {
81                         uint64_t interface_type : 4;
82                         uint64_t interface_version : 4;
83                         uint64_t cap_locality : 1;
84                         uint64_t cap_crb_idle_bypass : 1;
85                         uint64_t _reserved1 : 1;
86                         uint64_t cap_data_xfer_size_support : 2;
87                         uint64_t cap_fifo : 1;
88                         uint64_t cap_crb : 1;
89                         uint64_t _reserved2 : 2;
90                         uint64_t interface_selector : 2;
91                         uint64_t intf_sel_lock : 1;
92                         uint64_t _reserved3 : 4;
93                         uint64_t rid : 8;
94                         uint64_t vid : 16;
95                         uint64_t did : 16;
96                 };
97                 uint64_t val;
98         } intf_id; /* 30h */
99         union tpm_crb_reg_ctrl_ext {
100                 struct {
101                         uint32_t clear;
102                         uint32_t remaining_bytes;
103                 };
104                 uint64_t val;
105         } ctrl_ext; /* 38 */
106         union tpm_crb_reg_ctrl_req {
107                 struct {
108                         uint32_t cmd_ready : 1;
109                         uint32_t go_idle : 1;
110                 };
111                 uint32_t val;
112         } ctrl_req; /* 40h */
113         union tpm_crb_reg_ctrl_sts {
114                 struct {
115                         uint32_t tpm_sts : 1;
116                         uint32_t tpm_idle : 1;
117                 };
118                 uint32_t val;
119         } ctrl_sts; /* 44h */
120         union tpm_crb_reg_ctrl_cancel {
121                 struct {
122                         uint32_t cancel : 1;
123                 };
124                 uint32_t val;
125         } ctrl_cancel; /* 48h */
126         union tpm_crb_reg_ctrl_start {
127                 struct {
128                         uint32_t start : 1;
129                 };
130                 uint32_t val;
131         } ctrl_start;                                  /* 4Ch*/
132         uint32_t int_enable;                           /* 50h */
133         uint32_t int_sts;                              /* 54h */
134         uint32_t cmd_size;                             /* 58h */
135         uint32_t cmd_addr_lo;                          /* 5Ch */
136         uint32_t cmd_addr_hi;                          /* 60h */
137         uint32_t rsp_size;                             /* 64h */
138         uint64_t rsp_addr;                             /* 68h */
139         uint8_t _reserved3[0x10];                      /* 70h */
140         uint8_t data_buffer[TPM_CRB_DATA_BUFFER_SIZE]; /* 80h */
141 } __packed;
142 static_assert(sizeof(struct tpm_crb_regs) == TPM_CRB_REGS_SIZE,
143     "Invalid size of tpm_crb");
144
145 #define CRB_CMD_SIZE_READ(regs) (regs.cmd_size)
146 #define CRB_CMD_SIZE_WRITE(regs, val) \
147         do {                          \
148                 regs.cmd_size = val;  \
149         } while (0)
150 #define CRB_CMD_ADDR_READ(regs) \
151         (((uint64_t)regs.cmd_addr_hi << 32) | regs.cmd_addr_lo)
152 #define CRB_CMD_ADDR_WRITE(regs, val)                \
153         do {                                         \
154                 regs.cmd_addr_lo = val & 0xFFFFFFFF; \
155                 regs.cmd_addr_hi = val >> 32;        \
156         } while (0)
157 #define CRB_RSP_SIZE_READ(regs) (regs.rsp_size)
158 #define CRB_RSP_SIZE_WRITE(regs, val) \
159         do {                          \
160                 regs.rsp_size = val;  \
161         } while (0)
162 #define CRB_RSP_ADDR_READ(regs) (regs.rsp_addr)
163 #define CRB_RSP_ADDR_WRITE(regs, val) \
164         do {                          \
165                 regs.rsp_addr = val;  \
166         } while (0)
167
168 struct tpm_crb {
169         struct tpm_emul *emul;
170         void *emul_sc;
171         uint8_t tpm_log_area[TPM_CRB_LOG_AREA_MINIMUM_SIZE];
172         struct tpm_crb_regs regs;
173         pthread_t thread;
174         pthread_mutex_t mutex;
175         pthread_cond_t cond;
176         bool closing;
177 };
178
179 static void *
180 tpm_crb_thread(void *const arg)
181 {
182         struct tpm_crb *const crb = arg;
183
184         pthread_mutex_lock(&crb->mutex);
185         for (;;) {
186                 /*
187                  * We're releasing the lock after wake up. Therefore, we have to
188                  * check the closing condition before and after going to sleep.
189                  */
190                 if (crb->closing)
191                         break;
192
193                 pthread_cond_wait(&crb->cond, &crb->mutex);
194
195                 if (crb->closing)
196                         break;
197
198                 const uint64_t cmd_addr = CRB_CMD_ADDR_READ(crb->regs);
199                 const uint64_t rsp_addr = CRB_RSP_ADDR_READ(crb->regs);
200                 const uint32_t cmd_size = CRB_CMD_SIZE_READ(crb->regs);
201                 const uint32_t rsp_size = CRB_RSP_SIZE_READ(crb->regs);
202
203                 const uint64_t cmd_off = cmd_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
204                 const uint64_t rsp_off = rsp_addr - TPM_CRB_DATA_BUFFER_ADDRESS;
205
206                 if (cmd_off > TPM_CRB_DATA_BUFFER_SIZE ||
207                     cmd_off + cmd_size > TPM_CRB_DATA_BUFFER_SIZE ||
208                     rsp_off > TPM_CRB_DATA_BUFFER_SIZE ||
209                     rsp_off + rsp_size > TPM_CRB_DATA_BUFFER_SIZE) {
210                         warnx(
211                             "%s: invalid cmd [%16lx, %16lx] --> [%16lx, %16lx]\n\r",
212                             __func__, cmd_addr, cmd_addr + cmd_size, rsp_addr,
213                             rsp_addr + rsp_size);
214                         break;
215                 }
216
217                 uint8_t cmd[TPM_CRB_DATA_BUFFER_SIZE];
218                 memcpy(cmd, crb->regs.data_buffer, TPM_CRB_DATA_BUFFER_SIZE);
219
220                 /*
221                  * A TPM command can take multiple seconds to execute. As we've
222                  * copied all required values and buffers at this point, we can
223                  * release the mutex.
224                  */
225                 pthread_mutex_unlock(&crb->mutex);
226
227                 /*
228                  * The command response buffer interface uses a single buffer
229                  * for sending a command to and receiving a response from the
230                  * tpm. To avoid reading old data from the command buffer which
231                  * might be a security issue, we zero out the command buffer
232                  * before writing the response into it. The rsp_size parameter
233                  * is controlled by the guest and it's not guaranteed that the
234                  * response has a size of rsp_size (e.g. if the tpm returned an
235                  * error, the response would have a different size than
236                  * expected). For that reason, use a second buffer for the
237                  * response.
238                  */
239                 uint8_t rsp[TPM_CRB_DATA_BUFFER_SIZE] = { 0 };
240                 crb->emul->execute_cmd(crb->emul_sc, &cmd[cmd_off], cmd_size,
241                     &rsp[rsp_off], rsp_size);
242
243                 pthread_mutex_lock(&crb->mutex);
244                 memset(crb->regs.data_buffer, 0, TPM_CRB_DATA_BUFFER_SIZE);
245                 memcpy(&crb->regs.data_buffer[rsp_off], &rsp[rsp_off], rsp_size);
246
247                 crb->regs.ctrl_start.start = false;
248         }
249         pthread_mutex_unlock(&crb->mutex);
250
251         return (NULL);
252 }
253
254 static int
255 tpm_crb_mmiocpy(void *const dst, void *const src, const int size)
256 {
257         if (!(size == 1 || size == 2 || size == 4 || size == 8))
258                 return (EINVAL);
259         memcpy(dst, src, size);
260
261         return (0);
262 }
263
264 static int
265 tpm_crb_mem_handler(struct vcpu *vcpu __unused, const int dir,
266     const uint64_t addr, const int size, uint64_t *const val, void *const arg1,
267     const long arg2 __unused)
268 {
269         struct tpm_crb *crb;
270         uint8_t *ptr;
271         uint64_t off, shift;
272         int error = 0;
273
274         if ((addr & (size - 1)) != 0) {
275                 warnx("%s: unaligned %s access @ %16lx [size = %x]", __func__,
276                     (dir == MEM_F_READ) ? "read" : "write", addr, size);
277                 return (EINVAL);
278         }
279
280         crb = arg1;
281
282         off = addr - TPM_CRB_ADDRESS;
283         if (off > TPM_CRB_REGS_SIZE || off + size >= TPM_CRB_REGS_SIZE) {
284                 return (EINVAL);
285         }
286
287         shift = 8 * (off & 3);
288         ptr = (uint8_t *)&crb->regs + off;
289
290         if (dir == MEM_F_READ) {
291                 error = tpm_crb_mmiocpy(val, ptr, size);
292                 if (error)
293                         goto err_out;
294         } else {
295                 switch (off & ~0x3) {
296                 case offsetof(struct tpm_crb_regs, loc_ctrl): {
297                         union tpm_crb_reg_loc_ctrl loc_ctrl;
298
299                         if ((size_t)size > sizeof(loc_ctrl))
300                                 goto err_out;
301
302                         *val = *val << shift;
303                         tpm_crb_mmiocpy(&loc_ctrl, val, size);
304
305                         if (loc_ctrl.relinquish) {
306                                 crb->regs.loc_sts.granted = false;
307                                 crb->regs.loc_state.loc_assigned = false;
308                         } else if (loc_ctrl.request_access) {
309                                 crb->regs.loc_sts.granted = true;
310                                 crb->regs.loc_state.loc_assigned = true;
311                         }
312
313                         break;
314                 }
315                 case offsetof(struct tpm_crb_regs, ctrl_req): {
316                         union tpm_crb_reg_ctrl_req req;
317
318                         if ((size_t)size > sizeof(req))
319                                 goto err_out;
320
321                         *val = *val << shift;
322                         tpm_crb_mmiocpy(&req, val, size);
323
324                         if (req.cmd_ready && !req.go_idle) {
325                                 crb->regs.ctrl_sts.tpm_idle = false;
326                         } else if (!req.cmd_ready && req.go_idle) {
327                                 crb->regs.ctrl_sts.tpm_idle = true;
328                         }
329
330                         break;
331                 }
332                 case offsetof(struct tpm_crb_regs, ctrl_cancel): {
333                         /* TODO: cancel the tpm command */
334                         warnx(
335                             "%s: cancelling a TPM command is not implemented yet",
336                             __func__);
337
338                         break;
339                 }
340                 case offsetof(struct tpm_crb_regs, ctrl_start): {
341                         union tpm_crb_reg_ctrl_start start;
342
343                         if ((size_t)size > sizeof(start))
344                                 goto err_out;
345
346                         *val = *val << shift;
347
348                         pthread_mutex_lock(&crb->mutex);
349                         tpm_crb_mmiocpy(&start, val, size);
350
351                         if (!start.start || crb->regs.ctrl_start.start)
352                                 break;
353
354                         crb->regs.ctrl_start.start = true;
355
356                         pthread_cond_signal(&crb->cond);
357                         pthread_mutex_unlock(&crb->mutex);
358
359                         break;
360                 }
361                 case offsetof(struct tpm_crb_regs, cmd_size):
362                 case offsetof(struct tpm_crb_regs, cmd_addr_lo):
363                 case offsetof(struct tpm_crb_regs, cmd_addr_hi):
364                 case offsetof(struct tpm_crb_regs, rsp_size):
365                 case offsetof(struct tpm_crb_regs,
366                     rsp_addr) ... offsetof(struct tpm_crb_regs, rsp_addr) +
367                     4:
368                 case offsetof(struct tpm_crb_regs,
369                     data_buffer) ... offsetof(struct tpm_crb_regs, data_buffer) +
370                     TPM_CRB_DATA_BUFFER_SIZE / 4:
371                         /*
372                          * Those fields are used to execute a TPM command. The
373                          * crb_thread will access them. For that reason, we have
374                          * to acquire the crb mutex in order to write them.
375                          */
376                         pthread_mutex_lock(&crb->mutex);
377                         error = tpm_crb_mmiocpy(ptr, val, size);
378                         pthread_mutex_unlock(&crb->mutex);
379                         if (error)
380                                 goto err_out;
381                         break;
382                 default:
383                         /*
384                          * The other fields are either readonly or we do not
385                          * support writing them.
386                          */
387                         error = EINVAL;
388                         goto err_out;
389                 }
390         }
391
392         return (0);
393
394 err_out:
395         warnx("%s: invalid %s @ %16lx [size = %d]", __func__,
396             dir == MEM_F_READ ? "read" : "write", addr, size);
397
398         return (error);
399 }
400
401 static int
402 tpm_crb_modify_mmio_registration(const bool registration, void *const arg1)
403 {
404         struct mem_range crb_mmio = {
405                 .name = "crb-mmio",
406                 .base = TPM_CRB_ADDRESS,
407                 .size = TPM_CRB_LOCALITIES_MAX * TPM_CRB_CONTROL_AREA_SIZE,
408                 .flags = MEM_F_RW,
409                 .arg1 = arg1,
410                 .handler = tpm_crb_mem_handler,
411         };
412
413         if (registration)
414                 return (register_mem(&crb_mmio));
415         else
416                 return (unregister_mem(&crb_mmio));
417 }
418
419 static int
420 tpm_crb_init(void **sc, struct tpm_emul *emul, void *emul_sc,
421     struct acpi_device *acpi_dev)
422 {
423         struct tpm_crb *crb = NULL;
424         int error;
425
426         assert(sc != NULL);
427         assert(emul != NULL);
428
429         crb = calloc(1, sizeof(struct tpm_crb));
430         if (crb == NULL) {
431                 warnx("%s: failed to allocate tpm crb", __func__);
432                 error = ENOMEM;
433                 goto err_out;
434         }
435
436         memset(crb, 0, sizeof(*crb));
437
438         crb->emul = emul;
439         crb->emul_sc = emul_sc;
440
441         crb->regs.loc_state.tpm_req_valid_sts = true;
442         crb->regs.loc_state.tpm_established = true;
443
444         crb->regs.intf_id.interface_type = TPM_INTF_TYPE_CRB;
445         crb->regs.intf_id.interface_version = TPM_INTF_VERSION_CRB;
446         crb->regs.intf_id.cap_locality = false;
447         crb->regs.intf_id.cap_crb_idle_bypass = false;
448         crb->regs.intf_id.cap_data_xfer_size_support =
449             TPM_INTF_CAP_CRB_DATA_XFER_SIZE_64;
450         crb->regs.intf_id.cap_fifo = false;
451         crb->regs.intf_id.cap_crb = true;
452         crb->regs.intf_id.interface_selector = TPM_INTF_SELECTOR_CRB;
453         crb->regs.intf_id.intf_sel_lock = false;
454         crb->regs.intf_id.rid = 0;
455         crb->regs.intf_id.vid = 0x1014; /* IBM */
456         crb->regs.intf_id.did = 0x1014; /* IBM */
457
458         crb->regs.ctrl_sts.tpm_idle = true;
459
460         CRB_CMD_SIZE_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_SIZE);
461         CRB_CMD_ADDR_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_ADDRESS);
462         CRB_RSP_SIZE_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_SIZE);
463         CRB_RSP_ADDR_WRITE(crb->regs, TPM_CRB_DATA_BUFFER_ADDRESS);
464
465         error = qemu_fwcfg_add_file(TPM_CRB_LOG_AREA_FWCFG_NAME,
466             TPM_CRB_LOG_AREA_MINIMUM_SIZE, crb->tpm_log_area);
467         if (error) {
468                 warnx("%s: failed to add fwcfg file", __func__);
469                 goto err_out;
470         }
471
472         error = acpi_device_add_res_fixed_memory32(acpi_dev, false,
473             TPM_CRB_ADDRESS, TPM_CRB_CONTROL_AREA_SIZE);
474         if (error) {
475                 warnx("%s: failed to add acpi resources\n", __func__);
476                 goto err_out;
477         }
478
479         error = tpm_crb_modify_mmio_registration(true, crb);
480         if (error) {
481                 warnx("%s: failed to register crb mmio", __func__);
482                 goto err_out;
483         }
484
485         error = pthread_mutex_init(&crb->mutex, NULL);
486         if (error) {
487                 warnc(error, "%s: failed to init mutex", __func__);
488                 goto err_out;
489         }
490
491         error = pthread_cond_init(&crb->cond, NULL);
492         if (error) {
493                 warnc(error, "%s: failed to init cond", __func__);
494                 goto err_out;
495         }
496
497         error = pthread_create(&crb->thread, NULL, tpm_crb_thread, crb);
498         if (error) {
499                 warnx("%s: failed to create thread\n", __func__);
500                 goto err_out;
501         }
502
503         pthread_set_name_np(crb->thread, "tpm_intf_crb");
504
505         *sc = crb;
506
507         return (0);
508
509 err_out:
510         free(crb);
511
512         return (error);
513 }
514
515 static void
516 tpm_crb_deinit(void *sc)
517 {
518         struct tpm_crb *crb;
519         int error;
520
521         if (sc == NULL) {
522                 return;
523         }
524
525         crb = sc;
526
527         crb->closing = true;
528         pthread_cond_signal(&crb->cond);
529         pthread_join(crb->thread, NULL);
530
531         pthread_cond_destroy(&crb->cond);
532         pthread_mutex_destroy(&crb->mutex);
533
534         error = tpm_crb_modify_mmio_registration(false, NULL);
535         assert(error == 0);
536
537         free(crb);
538 }
539
540 static int
541 tpm_crb_build_acpi_table(void *sc __unused, struct vmctx *vm_ctx)
542 {
543         struct basl_table *table;
544
545         BASL_EXEC(basl_table_create(&table, vm_ctx, ACPI_SIG_TPM2,
546             BASL_TABLE_ALIGNMENT));
547
548         /* Header */
549         BASL_EXEC(basl_table_append_header(table, ACPI_SIG_TPM2, 4, 1));
550         /* Platform Class */
551         BASL_EXEC(basl_table_append_int(table, 0, 2));
552         /* Reserved */
553         BASL_EXEC(basl_table_append_int(table, 0, 2));
554         /* Control Address */
555         BASL_EXEC(
556             basl_table_append_int(table, TPM_CRB_CONTROL_AREA_ADDRESS, 8));
557         /* Start Method == (7) Command Response Buffer */
558         BASL_EXEC(basl_table_append_int(table, 7, 4));
559         /* Start Method Specific Parameters */
560         uint8_t parameters[12] = { 0 };
561         BASL_EXEC(basl_table_append_bytes(table, parameters, 12));
562         /* Log Area Minimum Length */
563         BASL_EXEC(
564             basl_table_append_int(table, TPM_CRB_LOG_AREA_MINIMUM_SIZE, 4));
565         /* Log Area Start Address */
566         BASL_EXEC(
567             basl_table_append_fwcfg(table, TPM_CRB_LOG_AREA_FWCFG_NAME, 1, 8));
568
569         BASL_EXEC(basl_table_register_to_rsdt(table));
570
571         return (0);
572 }
573
574 static struct tpm_intf tpm_intf_crb = {
575         .name = TPM_CRB_INTF_NAME,
576         .init = tpm_crb_init,
577         .deinit = tpm_crb_deinit,
578         .build_acpi_table = tpm_crb_build_acpi_table,
579 };
580 TPM_INTF_SET(tpm_intf_crb);