/* SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include "tpm.h" #define RECV_TIMEOUT (1 * 1000) /* 1 second */ #define XMIT_TIMEOUT (1 * 1000) /* 1 second */ #define SLEEP_DURATION 1000 /* microseconds */ struct tpm_output_header { uint16_t tag; uint32_t length; uint32_t return_code; } __packed; static tpm_result_t i2c_tis_sendrecv(const uint8_t *sendbuf, size_t sbuf_size, uint8_t *recvbuf, size_t *rbuf_len) { size_t hdr_bytes; struct tpm_output_header *header; size_t max_recv_bytes; size_t recv_bytes; int status; struct stopwatch sw; ASSERT(sbuf_size >= 10); if (CONFIG(DRIVER_TPM_DISPLAY_TIS_BYTES)) { /* Display the TPM command */ if (sbuf_size >= 10) printk(BIOS_DEBUG, "TPM Command: 0x%08x\n", read_at_be32(sendbuf, sizeof(uint16_t) + sizeof(uint32_t))); hexdump(sendbuf, sbuf_size); } /* Send the command to the TPM */ stopwatch_init_msecs_expire(&sw, XMIT_TIMEOUT); while (1) { status = i2c_write_raw(CONFIG_DRIVER_TPM_I2C_BUS, CONFIG_DRIVER_TPM_I2C_ADDR, (uint8_t *)sendbuf, sbuf_size); if ((status < 0) && (!stopwatch_expired(&sw))) continue; if (status < 0) { printk(BIOS_ERR, "I2C write error: %d\n", status); return TPM_CB_COMMUNICATION_ERROR; } break; } /* Read the TPM response header */ max_recv_bytes = *rbuf_len; ASSERT(max_recv_bytes >= sizeof(*header)); hdr_bytes = sizeof(*header); header = (struct tpm_output_header *)recvbuf; stopwatch_init_msecs_expire(&sw, RECV_TIMEOUT); do { status = i2c_read_raw(CONFIG_DRIVER_TPM_I2C_BUS, CONFIG_DRIVER_TPM_I2C_ADDR, recvbuf, hdr_bytes); if (status > 0) break; udelay(SLEEP_DURATION); } while (!stopwatch_expired(&sw)); if (status != sizeof(*header)) return TPM_CB_COMMUNICATION_ERROR; /* Determine the number of bytes remaining */ recv_bytes = MIN(be32_to_cpu(*(uint32_t *)&header->length), max_recv_bytes); /* Determine if there is additional response data */ if (recv_bytes > hdr_bytes) { /* Display the TPM response */ if (CONFIG(DRIVER_TPM_DISPLAY_TIS_BYTES)) hexdump(recvbuf, hdr_bytes); /* Read the full TPM response */ status = i2c_read_raw(CONFIG_DRIVER_TPM_I2C_BUS, CONFIG_DRIVER_TPM_I2C_ADDR, recvbuf, recv_bytes); if (status < 0) { printk(BIOS_ERR, "I2C read error: %d\n", status); return TPM_CB_COMMUNICATION_ERROR; } } /* Return the number of bytes received */ *rbuf_len = status; /* Display the TPM response */ if (CONFIG(DRIVER_TPM_DISPLAY_TIS_BYTES)) { printk(BIOS_DEBUG, "TPM Response: 0x%08x\n", read_at_be32(recvbuf, sizeof(uint16_t) + sizeof(uint32_t))); hexdump(recvbuf, *rbuf_len); } /* Successful transfer */ return TPM_SUCCESS; } tis_sendrecv_fn i2c_tis_probe(enum tpm_family *family) { /* * Can't query version or really anything as the device doesn't support * much through this interface (can't specify address on accesses). * * Hence the assumption is that whatever TPM version is enabled at * compile-time defines what the device supports. The check is written * in a way to give TPM 1 preference even if support for both versions * is compiled in. */ if (family != NULL) *family = CONFIG(TPM1) ? TPM_1 : TPM_2; return &i2c_tis_sendrecv; }