Index: sys/conf/files.amd64 =================================================================== --- sys/conf/files.amd64 +++ sys/conf/files.amd64 @@ -488,6 +488,9 @@ dev/syscons/scvgarndr.c optional sc vga dev/syscons/scvtb.c optional sc dev/tpm/tpm.c optional tpm +dev/tpm/tpm20.c optional tpm +dev/tpm/tpm_crb.c optional tpm acpi +dev/tpm/tpm_tis.c optional tpm acpi dev/tpm/tpm_acpi.c optional tpm acpi dev/tpm/tpm_isa.c optional tpm isa dev/uart/uart_cpu_x86.c optional uart Index: sys/dev/tpm/tpm20.h =================================================================== --- /dev/null +++ sys/dev/tpm/tpm20.h @@ -0,0 +1,182 @@ +/*- + * Copyright (c) 2018 Stormshield. + * Copyright (c) 2018 Semihalf. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TPM20_H_ +#define _TPM20_H_ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include "opt_acpi.h" + +#define BIT(x) (1 << (x)) + +/* Timeouts in us */ +#define TPM_TIMEOUT_A 750000 +#define TPM_TIMEOUT_B 2000000 +#define TPM_TIMEOUT_C 200000 +#define TPM_TIMEOUT_D 30000 + +/* + * Generating RSA key pair takes ~(10-20s), which is significantly longer than + * any timeout defined in spec. Because of that we need a new one. + */ +#define TPM_TIMEOUT_LONG 40000000 + +/* List of commands that require TPM_TIME_LONG time to complete */ +#define TPM_CC_CreatePrimary 0x00000131 +#define TPM_CC_Create 0x00000153 +#define TPM_CC_CreateLoaded 0x00000191 + +/* Timeout before data in read buffer is discarded */ +#define TPM_READ_TIMEOUT 500000 + +#define TPM_BUFSIZE 0x1000 + +#define TPM_HEADER_SIZE 10 + +#define TPM_CDEV_NAME "tpm0" +#define TPM_CDEV_PERM_FLAG 0600 + +struct tpm_sc { + device_t dev; + + struct resource *mem_res; + struct resource *irq_res; + int mem_rid; + int irq_rid; + + struct cdev *sc_cdev; + + struct sx dev_lock; + struct cv buf_cv; + + void *intr_cookie; + int intr_type; /* Current event type */ + bool interrupts; + + uint8_t *buf; + size_t pending_data_length; + + struct callout discard_buffer_callout; + + int (*transmit)(struct tpm_sc *, size_t); +}; + +int tpm20_save_state(device_t dev); +int32_t tpm20_get_timeout(uint32_t command); +int tpm20_init(struct tpm_sc *sc); +void tpm20_release(struct tpm_sc *sc); + +d_open_t tpm20_open; +d_close_t tpm20_close; +d_read_t tpm20_read; +d_write_t tpm20_write; +d_ioctl_t tpm20_ioctl; + +static struct cdevsw tpm_cdevsw = { + .d_version = D_VERSION, + .d_open = tpm20_open, + .d_close = tpm20_close, + .d_read = tpm20_read, + .d_write = tpm20_write, + .d_ioctl = tpm20_ioctl, + .d_name = "tpm20", +}; + +/* Small helper routines for io ops */ +static inline uint8_t +RD1(struct tpm_sc *sc, bus_size_t off) +{ + + return (bus_read_1(sc->mem_res, off)); +} +static inline uint32_t +RD4(struct tpm_sc *sc, bus_size_t off) +{ + + return (bus_read_4(sc->mem_res, off)); +} +static inline uint64_t +RD8(struct tpm_sc *sc, bus_size_t off) +{ + + return (bus_read_8(sc->mem_res, off)); +} +static inline void +WR1(struct tpm_sc *sc, bus_size_t off, uint8_t val) +{ + + bus_write_1(sc->mem_res, off, val); +} +static inline void +WR4(struct tpm_sc *sc, bus_size_t off, uint32_t val) +{ + + bus_write_4(sc->mem_res, off, val); +} +static inline void +AND4(struct tpm_sc *sc, bus_size_t off, uint32_t val) +{ + + WR4(sc, off, RD4(sc, off) & val); +} +static inline void +OR1(struct tpm_sc *sc, bus_size_t off, uint8_t val) +{ + + WR1(sc, off, RD1(sc, off) | val); +} +static inline void +OR4(struct tpm_sc *sc, bus_size_t off, uint32_t val) +{ + + WR4(sc, off, RD4(sc, off) | val); +} +#endif /* _TPM20_H_ */ Index: sys/dev/tpm/tpm20.c =================================================================== --- /dev/null +++ sys/dev/tpm/tpm20.c @@ -0,0 +1,229 @@ +/*- + * Copyright (c) 2018 Stormshield. + * Copyright (c) 2018 Semihalf. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tpm20.h" + +MALLOC_DECLARE(M_TPM20); +MALLOC_DEFINE(M_TPM20, "tpm_buffer", "buffer for tpm 2.0 driver"); + +static void tpm20_discard_buffer(void *arg); + +int +tpm20_read(struct cdev *dev, struct uio *uio, int flags) +{ + struct tpm_sc *sc; + size_t bytes_to_transfer; + int result = 0; + + sc = (struct tpm_sc *)dev->si_drv1; + + callout_stop(&sc->discard_buffer_callout); + sx_xlock(&sc->dev_lock); + + bytes_to_transfer = MIN(sc->pending_data_length, uio->uio_resid); + if (bytes_to_transfer > 0) { + result = uiomove((caddr_t) sc->buf, bytes_to_transfer, uio); + memset(sc->buf, 0, TPM_BUFSIZE); + sc->pending_data_length = 0; + cv_signal(&sc->buf_cv); + } else { + result = ETIMEDOUT; + } + + sx_xunlock(&sc->dev_lock); + + return (result); +} + +int +tpm20_write(struct cdev *dev, struct uio *uio, int flags) +{ + struct tpm_sc *sc; + size_t byte_count; + int result = 0; + + sc = (struct tpm_sc *)dev->si_drv1; + + byte_count = uio->uio_resid; + if (byte_count < TPM_HEADER_SIZE) { + device_printf(sc->dev, + "Requested transfer is too small\n"); + return (EINVAL); + } + + if (byte_count > TPM_BUFSIZE) { + device_printf(sc->dev, + "Requested transfer is too large\n"); + return (E2BIG); + } + + sx_xlock(&sc->dev_lock); + + while (sc->pending_data_length != 0) + cv_wait(&sc->buf_cv, &sc->dev_lock); + + result = uiomove(sc->buf, byte_count, uio); + if (result != 0) { + sx_xunlock(&sc->dev_lock); + return (result); + } + + result = sc->transmit(sc, byte_count); + + if (result == 0) + callout_reset(&sc->discard_buffer_callout, + TPM_READ_TIMEOUT / tick, tpm20_discard_buffer, sc); + + sx_xunlock(&sc->dev_lock); + return (result); +} + +static void tpm20_discard_buffer(void *arg) +{ + struct tpm_sc *sc; + + sc = (struct tpm_sc *)arg; + if (callout_pending(&sc->discard_buffer_callout)) + return; + + sx_xlock(&sc->dev_lock); + + memset(sc->buf, 0, TPM_BUFSIZE); + sc->pending_data_length = 0; + + cv_signal(&sc->buf_cv); + sx_xunlock(&sc->dev_lock); + + device_printf(sc->dev, + "User failed to read buffer in time\n"); +} + +int +tpm20_open(struct cdev *dev, int flag, int mode, struct thread *td) +{ + + return (0); +} + +int +tpm20_close(struct cdev *dev, int flag, int mode, struct thread *td) +{ + + return (0); +} + + +int +tpm20_ioctl(struct cdev *dev, u_long cmd, caddr_t data, + int flags, struct thread *td) +{ + + return (ENOTTY); +} + +int +tpm20_init(struct tpm_sc *sc) +{ + struct make_dev_args args; + int result; + + sc->buf = malloc(TPM_BUFSIZE, M_TPM20, M_WAITOK); + sx_init(&sc->dev_lock, "TPM driver lock"); + cv_init(&sc->buf_cv, "TPM buffer cv"); + callout_init(&sc->discard_buffer_callout, 1); + sc->pending_data_length = 0; + + make_dev_args_init(&args); + args.mda_devsw = &tpm_cdevsw; + args.mda_uid = UID_ROOT; + args.mda_gid = GID_WHEEL; + args.mda_mode = TPM_CDEV_PERM_FLAG; + args.mda_si_drv1 = sc; + result = make_dev_s(&args, &sc->sc_cdev, TPM_CDEV_NAME); + if (result != 0) + tpm20_release(sc); + + return (result); + +} + +void +tpm20_release(struct tpm_sc *sc) +{ + + if (sc->buf != NULL) + free(sc->buf, M_TPM20); + + sx_destroy(&sc->dev_lock); + cv_destroy(&sc->buf_cv); + if (sc->sc_cdev != NULL) + destroy_dev(sc->sc_cdev); +} + + +int +tpm20_save_state(device_t dev) +{ + struct tpm_sc *sc; + uint8_t save_cmd[] = { + 0x80, 0x01, /* TPM_ST_NO_SESSIONS tag*/ + 0x00, 0x00, 0x00, 0x0C, /* cmd length */ + 0x00, 0x00, 0x01, 0x45, 0x00, 0x00 /* cmd TPM_CC_Shutdown */ + }; + + sc = device_get_softc(dev); + + if (sc == NULL || sc->buf == NULL) + return (0); + + sx_xlock(&sc->dev_lock); + + memcpy(sc->buf, save_cmd, sizeof(save_cmd)); + sc->transmit(sc, sizeof(save_cmd)); + + sx_xunlock(&sc->dev_lock); + + return (0); +} + +int32_t +tpm20_get_timeout(uint32_t command) +{ + int32_t timeout; + + switch (command) { + case TPM_CC_CreatePrimary: + case TPM_CC_Create: + case TPM_CC_CreateLoaded: + timeout = TPM_TIMEOUT_LONG; + break; + default: + timeout = TPM_TIMEOUT_B; + break; + } + return timeout; +} Index: sys/dev/tpm/tpm_crb.c =================================================================== --- /dev/null +++ sys/dev/tpm/tpm_crb.c @@ -0,0 +1,416 @@ +/*- + * Copyright (c) 2018 Stormshield. + * Copyright (c) 2018 Semihalf. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tpm20.h" + +/* + * CRB register space as defined in + * TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22 + */ +#define TPM_LOC_STATE 0x0 +#define TPM_LOC_CTRL 0x8 +#define TPM_LOC_STS 0xC +#define TPM_CRB_INTF_ID 0x30 +#define TPM_CRB_CTRL_EXT 0x38 +#define TPM_CRB_CTRL_REQ 0x40 +#define TPM_CRB_CTRL_STS 0x44 +#define TPM_CRB_CTRL_CANCEL 0x48 +#define TPM_CRB_CTRL_START 0x4C +#define TPM_CRB_INT_ENABLE 0x50 +#define TPM_CRB_INT_STS 0x54 +#define TPM_CRB_CTRL_CMD_SIZE 0x58 +#define TPM_CRB_CTRL_CMD_LADDR 0x5C +#define TPM_CRB_CTRL_CMD_HADDR 0x60 +#define TPM_CRB_CTRL_RSP_SIZE 0x64 +#define TPM_CRB_CTRL_RSP_ADDR 0x68 +#define TPM_CRB_DATA_BUFFER 0x80 + +#define TPM_LOC_STATE_ESTB BIT(0) +#define TPM_LOC_STATE_ASSIGNED BIT(1) +#define TPM_LOC_STATE_ACTIVE_MASK 0x9C +#define TPM_LOC_STATE_VALID BIT(7) + +#define TPM_CRB_INTF_ID_TYPE_CRB 0x1 +#define TPM_CRB_INTF_ID_TYPE 0x7 + +#define TPM_LOC_CTRL_REQUEST BIT(0) +#define TPM_LOC_CTRL_RELINQUISH BIT(1) + +#define TPM_CRB_CTRL_REQ_GO_READY BIT(0) +#define TPM_CRB_CTRL_REQ_GO_IDLE BIT(1) + +#define TPM_CRB_CTRL_STS_ERR_BIT BIT(0) +#define TPM_CRB_CTRL_STS_IDLE_BIT BIT(1) + +#define TPM_CRB_CTRL_CANCEL_CMD BIT(0) + +#define TPM_CRB_CTRL_START_CMD BIT(0) + +#define TPM_CRB_INT_ENABLE_BIT BIT(31) + +struct tpmcrb_sc { + struct tpm_sc base; + bus_size_t cmd_off; + bus_size_t rsp_off; + size_t cmd_buf_size; + size_t rsp_buf_size; +}; + + +int tpmcrb_transmit(struct tpm_sc *sc, size_t size); + +static int tpmcrb_acpi_probe(device_t dev); +static int tpmcrb_attach(device_t dev); +static int tpmcrb_detach(device_t dev); + +static ACPI_STATUS tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg); + +static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, + uint32_t mask, uint32_t val, int32_t timeout); +static bool tpmcrb_request_locality(struct tpm_sc *sc, int locality); +static void tpmcrb_relinquish_locality(struct tpm_sc *sc); +static bool tpmcrb_cancel_cmd(struct tpm_sc *sc); + +char *tpmcrb_ids[] = {"MSFT0101", NULL}; + +static int +tpmcrb_acpi_probe(device_t dev) +{ + struct resource *res; + int rid = 0; + uint32_t caps; + + if (ACPI_ID_PROBE(device_get_parent(dev), dev, tpmcrb_ids) == NULL) + return (ENXIO); + + /* Check if device is in CRB mode */ + res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (res == NULL) + return (ENXIO); + + caps = bus_read_4(res, TPM_CRB_INTF_ID); + bus_release_resource(dev, SYS_RES_MEMORY, rid, res); + + if ((caps & TPM_CRB_INTF_ID_TYPE) != TPM_CRB_INTF_ID_TYPE_CRB) + return (ENXIO); + + device_set_desc(dev, "Trusted Platform Module 2.0, CRB mode"); + return (BUS_PROBE_DEFAULT); +} + +static ACPI_STATUS +tpmcrb_fix_buff_offsets(ACPI_RESOURCE *res, void *arg) +{ + struct tpmcrb_sc *crb_sc; + size_t length; + uint32_t base_addr; + + crb_sc = (struct tpmcrb_sc *)arg; + + if (res->Type != ACPI_RESOURCE_TYPE_FIXED_MEMORY32) + return (AE_OK); + + base_addr = res->Data.FixedMemory32.Address; + length = res->Data.FixedMemory32.AddressLength; + + if (crb_sc->cmd_off > base_addr && crb_sc->cmd_off < base_addr + length) + crb_sc->cmd_off -= base_addr; + if (crb_sc->rsp_off > base_addr && crb_sc->rsp_off < base_addr + length) + crb_sc->rsp_off -= base_addr; + + return (AE_OK); +} + +static int +tpmcrb_attach(device_t dev) +{ + struct tpmcrb_sc *crb_sc; + struct tpm_sc *sc; + ACPI_HANDLE handle; + ACPI_STATUS status; + int result; + + crb_sc = device_get_softc(dev); + sc = &crb_sc->base; + handle = acpi_get_handle(dev); + + sc->dev = dev; + + sc->mem_rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, + RF_ACTIVE); + if (sc->mem_res == NULL) + return (ENXIO); + + if(!tpmcrb_request_locality(sc, 0)) { + tpmcrb_detach(dev); + return (ENXIO); + } + + /* + * Disable all interrupts for now, since I don't have a device that + * works in CRB mode and supports them. + */ + AND4(sc, TPM_CRB_INT_ENABLE, ~TPM_CRB_INT_ENABLE_BIT); + sc->interrupts = false; + + /* + * Read addresses of Tx/Rx buffers and their sizes. Note that they + * can be implemented by a single buffer. Also for some reason CMD + * addr is stored in two 4 byte neighboring registers, whereas RSP is + * stored in a single 8 byte one. + */ + crb_sc->rsp_off = RD8(sc, TPM_CRB_CTRL_RSP_ADDR); + crb_sc->cmd_off = RD4(sc, TPM_CRB_CTRL_CMD_LADDR); + crb_sc->cmd_off |= ((uint64_t) RD4(sc, TPM_CRB_CTRL_CMD_HADDR) << 32); + crb_sc->cmd_buf_size = RD4(sc, TPM_CRB_CTRL_CMD_SIZE); + crb_sc->rsp_buf_size = RD4(sc, TPM_CRB_CTRL_RSP_SIZE); + + tpmcrb_relinquish_locality(sc); + + /* Emulator returns address in acpi space instead of an offset */ + status = AcpiWalkResources(handle, "_CRS", tpmcrb_fix_buff_offsets, + (void *)crb_sc); + if (ACPI_FAILURE(status)) { + tpmcrb_detach(dev); + return (ENXIO); + } + + if (crb_sc->rsp_off == crb_sc->cmd_off) { + /* + * If Tx/Rx buffers are implemented as one they have to be of + * same size + */ + if (crb_sc->cmd_buf_size != crb_sc->rsp_buf_size) { + device_printf(sc->dev, + "Overlapping Tx/Rx buffers have different sizes\n"); + tpmcrb_detach(dev); + return (ENXIO); + } + } + + sc->transmit = tpmcrb_transmit; + + result = tpm20_init(sc); + if (result != 0) + tpmcrb_detach(dev); + + return (result); +} + +static int +tpmcrb_detach(device_t dev) +{ + struct tpm_sc *sc; + + sc = device_get_softc(dev); + + if (sc->mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->mem_rid, sc->mem_res); + + tpm20_release(sc); + return (0); +} + +static bool +tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val, + int32_t timeout) +{ + + /* Check for condition */ + if ((RD4(sc, off) & mask) == val) + return (true); + + while (timeout > 0) { + if ((RD4(sc, off) & mask) == val) + return (true); + + pause("TPM in polling mode", 1); + timeout -= tick; + } + return (false); +} + +static bool +tpmcrb_request_locality(struct tpm_sc *sc, int locality) +{ + uint32_t mask; + + /* Currently we only support Locality 0 */ + if (locality != 0) + return (false); + + mask = TPM_LOC_STATE_VALID | TPM_LOC_STATE_ASSIGNED; + + OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_REQUEST); + if (!tpm_wait_for_u32(sc, TPM_LOC_STATE, mask, mask, TPM_TIMEOUT_C)) + return (false); + + return (true); +} + +static void +tpmcrb_relinquish_locality(struct tpm_sc *sc) +{ + + OR4(sc, TPM_LOC_CTRL, TPM_LOC_CTRL_RELINQUISH); +} + +static bool +tpmcrb_cancel_cmd(struct tpm_sc *sc) +{ + uint32_t mask = ~0; + + WR4(sc, TPM_CRB_CTRL_CANCEL, TPM_CRB_CTRL_CANCEL_CMD); + if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, + mask, ~mask, TPM_TIMEOUT_B)) { + device_printf(sc->dev, + "Device failed to cancel command\n"); + return (false); + } + + WR4(sc, TPM_CRB_CTRL_CANCEL, !TPM_CRB_CTRL_CANCEL_CMD); + return (true); +} + +int +tpmcrb_transmit(struct tpm_sc *sc, size_t length) +{ + struct tpmcrb_sc *crb_sc; + uint32_t mask, curr_cmd; + int timeout, bytes_available; + + crb_sc = (struct tpmcrb_sc *)sc; + + sx_assert(&sc->dev_lock, SA_XLOCKED); + + if (length > crb_sc->cmd_buf_size) { + device_printf(sc->dev, + "Requested transfer is bigger than buffer size\n"); + return (E2BIG); + } + + if (RD4(sc, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_ERR_BIT) { + device_printf(sc->dev, + "Device has Error bit set\n"); + return (EIO); + } + if (!tpmcrb_request_locality(sc, 0)) { + device_printf(sc->dev, + "Failed to obtain locality\n"); + return (EIO); + } + /* Clear cancellation bit */ + WR4(sc, TPM_CRB_CTRL_CANCEL, !TPM_CRB_CTRL_CANCEL_CMD); + + /* Switch device to idle state if necessary */ + if (!(RD4(sc, TPM_CRB_CTRL_STS) & TPM_CRB_CTRL_STS_IDLE_BIT)) { + OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE); + + mask = TPM_CRB_CTRL_STS_IDLE_BIT; + if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, + mask, mask, TPM_TIMEOUT_C)) { + device_printf(sc->dev, + "Failed to transition to idle state\n"); + return (EIO); + } + } + /* Switch to ready state */ + OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_READY); + + mask = TPM_CRB_CTRL_REQ_GO_READY; + if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_STS, + mask, !mask, TPM_TIMEOUT_C)) { + device_printf(sc->dev, + "Failed to transition to ready state\n"); + return (EIO); + } + + /* + * Calculate timeout for current command. + * Command code is passed in bytes 6-10. + */ + curr_cmd = be32toh(*(uint32_t *) (&sc->buf[6])); + timeout = tpm20_get_timeout(curr_cmd); + + /* Send command and tell device to process it. */ + bus_write_region_stream_1(sc->mem_res, crb_sc->cmd_off, + sc->buf, length); + bus_barrier(sc->mem_res, crb_sc->cmd_off, + length, BUS_SPACE_BARRIER_WRITE); + + WR4(sc, TPM_CRB_CTRL_START, TPM_CRB_CTRL_START_CMD); + bus_barrier(sc->mem_res, TPM_CRB_CTRL_START, + 4, BUS_SPACE_BARRIER_WRITE); + + mask = ~0; + if (!tpm_wait_for_u32(sc, TPM_CRB_CTRL_START, mask, ~mask, timeout)) { + device_printf(sc->dev, + "Timeout while waiting for device to process cmd\n"); + if (!tpmcrb_cancel_cmd(sc)) + return (EIO); + } + + /* Read response header. Length is passed in bytes 2 - 6. */ + bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off, + sc->buf, TPM_HEADER_SIZE); + bytes_available = be32toh(*(uint32_t *) (&sc->buf[2])); + + if (bytes_available > TPM_BUFSIZE || bytes_available < TPM_HEADER_SIZE) { + device_printf(sc->dev, + "Incorrect response size: %d\n", + bytes_available); + return (EIO); + } + + bus_read_region_stream_1(sc->mem_res, crb_sc->rsp_off + TPM_HEADER_SIZE, + &sc->buf[TPM_HEADER_SIZE], bytes_available - TPM_HEADER_SIZE); + + OR4(sc, TPM_CRB_CTRL_REQ, TPM_CRB_CTRL_REQ_GO_IDLE); + + tpmcrb_relinquish_locality(sc); + sc->pending_data_length = bytes_available; + + return (0); +} + +/* ACPI Driver */ +static device_method_t tpmcrb_methods[] = { + DEVMETHOD(device_probe, tpmcrb_acpi_probe), + DEVMETHOD(device_attach, tpmcrb_attach), + DEVMETHOD(device_detach, tpmcrb_detach), + DEVMETHOD(device_shutdown, tpm20_save_state), + DEVMETHOD(device_suspend, tpm20_save_state), + {0, 0} +}; +static driver_t tpmcrb_driver = { + "tpmcrb", tpmcrb_methods, sizeof(struct tpmcrb_sc), +}; + +devclass_t tpmcrb_devclass; +DRIVER_MODULE(tpmcrb, acpi, tpmcrb_driver, tpmcrb_devclass, 0, 0); Index: sys/dev/tpm/tpm_tis.c =================================================================== --- /dev/null +++ sys/dev/tpm/tpm_tis.c @@ -0,0 +1,507 @@ +/*- + * Copyright (c) 2018 Stormshield. + * Copyright (c) 2018 Semihalf. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "tpm20.h" + +/* + * TIS register space as defined in + * TCG_PC_Client_Platform_TPM_Profile_PTP_2.0_r1.03_v22 + */ +#define TPM_ACCESS 0x0 +#define TPM_INT_ENABLE 0x8 +#define TPM_INT_VECTOR 0xc +#define TPM_INT_STS 0x10 +#define TPM_INTF_CAPS 0x14 +#define TPM_STS 0x18 +#define TPM_DATA_FIFO 0x24 +#define TPM_INTF_ID 0x30 +#define TPM_XDATA_FIFO 0x80 +#define TPM_DID_VID 0xF00 +#define TPM_RID 0xF04 + +#define TPM_ACCESS_LOC_REQ BIT(1) +#define TPM_ACCESS_LOC_Seize BIT(3) +#define TPM_ACCESS_LOC_ACTIVE BIT(5) +#define TPM_ACCESS_LOC_RELINQUISH BIT(5) +#define TPM_ACCESS_VALID BIT(7) + +#define TPM_INT_ENABLE_GLOBAL_ENABLE BIT(31) +#define TPM_INT_ENABLE_CMD_RDY BIT(7) +#define TPM_INT_ENABLE_LOC_CHANGE BIT(2) +#define TPM_INT_ENABLE_STS_VALID BIT(1) +#define TPM_INT_ENABLE_DATA_AVAIL BIT(0) + +#define TPM_INT_STS_CMD_RDY BIT(7) +#define TPM_INT_STS_LOC_CHANGE BIT(2) +#define TPM_INT_STS_VALID BIT(1) +#define TPM_INT_STS_DATA_AVAIL BIT(0) + +#define TPM_INTF_CAPS_VERSION 0x70000000 +#define TPM_INTF_CAPS_TPM20 0x30000000 + +#define TPM_STS_VALID BIT(7) +#define TPM_STS_CMD_RDY BIT(6) +#define TPM_STS_CMD_START BIT(5) +#define TPM_STS_DATA_AVAIL BIT(4) +#define TPM_STS_DATA_EXPECTED BIT(3) +#define TPM_STS_BURST_MASK 0xFFFF00 +#define TPM_STS_BURST_OFFSET 0x8 + +static int tpmtis_transmit(struct tpm_sc *sc, size_t length); + +static int tpmtis_acpi_probe(device_t dev); +static int tpmtis_attach(device_t dev); +static int tpmtis_detach(device_t dev); + +static void tpmtis_intr_handler(void *arg); + +static ACPI_STATUS tpmtis_get_SIRQ_channel(ACPI_RESOURCE *res, void *arg); +static bool tpmtis_setup_intr(struct tpm_sc *sc); + +static bool tpmtis_read_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf); +static bool tpmtis_write_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf); +static bool tpmtis_request_locality(struct tpm_sc *sc, int locality); +static void tpmtis_relinquish_locality(struct tpm_sc *sc); +static bool tpmtis_go_ready(struct tpm_sc *sc); + +static bool tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, + uint32_t mask, uint32_t val, int32_t timeout); +static uint16_t tpmtis_wait_for_burst(struct tpm_sc *sc); + +char *tpmtis_ids[] = {"MSFT0101", NULL}; + +static int +tpmtis_acpi_probe(device_t dev) +{ + struct resource *res; + int rid = 0; + uint32_t caps; + + if (ACPI_ID_PROBE(device_get_parent(dev), dev, tpmtis_ids) == NULL) + return (ENXIO); + + /* Check if device is in TPM 2.0 TIS mode */ + res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (res == NULL) + return (ENXIO); + + caps = bus_read_4(res, TPM_INTF_CAPS); + bus_release_resource(dev, SYS_RES_MEMORY, rid, res); + if ((caps & TPM_INTF_CAPS_VERSION) != TPM_INTF_CAPS_TPM20) + return (ENXIO); + + device_set_desc(dev, "Trusted Platform Module 2.0, FIFO mode"); + return (BUS_PROBE_DEFAULT); +} + +static int +tpmtis_attach(device_t dev) +{ + struct tpm_sc *sc; + int result; + + sc = device_get_softc(dev); + sc->dev = dev; + + sc->mem_rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, + RF_ACTIVE); + if (sc->mem_res == NULL) + return (ENXIO); + + sc->irq_rid = 0; + sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->irq_res != NULL) { + if (bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, tpmtis_intr_handler, sc, &sc->intr_cookie)) + sc->interrupts = false; + else + sc->interrupts = tpmtis_setup_intr(sc); + } else { + sc->interrupts = false; + } + + sc->intr_type = -1; + + sc->transmit = tpmtis_transmit; + + result = tpm20_init(sc); + if (result != 0) + tpmtis_detach(dev); + + return (result); +} + +static int +tpmtis_detach(device_t dev) +{ + struct tpm_sc *sc; + + sc = device_get_softc(dev); + + if (sc->intr_cookie != NULL) + bus_teardown_intr(dev, sc->irq_res, sc->intr_cookie); + + if (sc->irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, + sc->irq_rid, sc->irq_res); + + if (sc->mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + sc->mem_rid, sc->mem_res); + + tpm20_release(sc); + return (0); +} + +static ACPI_STATUS +tpmtis_get_SIRQ_channel(ACPI_RESOURCE *res, void *arg) +{ + struct tpm_sc *sc; + uint8_t channel; + + sc = (struct tpm_sc *)arg; + + switch (res->Type) { + case ACPI_RESOURCE_TYPE_IRQ: + channel = res->Data.Irq.Interrupts[0]; + break; + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + channel = res->Data.ExtendedIrq.Interrupts[0]; + break; + default: + return (AE_OK); + } + + WR1(sc, TPM_INT_VECTOR, channel); + return (AE_OK); +} + +static bool +tpmtis_setup_intr(struct tpm_sc *sc) +{ + ACPI_STATUS status; + ACPI_HANDLE handle; + uint32_t irq_mask; + + handle = acpi_get_handle(sc->dev); + + if(!tpmtis_request_locality(sc, 0)) + return (false); + + irq_mask = RD4(sc, TPM_INT_ENABLE); + irq_mask |= TPM_INT_ENABLE_GLOBAL_ENABLE | + TPM_INT_ENABLE_DATA_AVAIL | + TPM_INT_ENABLE_LOC_CHANGE | + TPM_INT_ENABLE_CMD_RDY | + TPM_INT_ENABLE_STS_VALID; + WR4(sc, TPM_INT_ENABLE, irq_mask); + + status = AcpiWalkResources(handle, "_CRS", + tpmtis_get_SIRQ_channel, (void *)sc); + + tpmtis_relinquish_locality(sc); + + return (ACPI_SUCCESS(status)); +} + +static void +tpmtis_intr_handler(void *arg) +{ + struct tpm_sc *sc; + uint32_t status; + + sc = (struct tpm_sc *)arg; + status = RD4(sc, TPM_INT_STS); + + WR4(sc, TPM_INT_STS, status); + if (sc->intr_type != -1 && sc->intr_type & status) + wakeup(sc); +} + +static bool +tpm_wait_for_u32(struct tpm_sc *sc, bus_size_t off, uint32_t mask, uint32_t val, + int32_t timeout) +{ + + /* Check for condition */ + if ((RD4(sc, off) & mask) == val) + return (true); + + /* If interrupts are enabled sleep for timeout duration */ + if(sc->interrupts && sc->intr_type != -1) { + tsleep(sc, PWAIT, "TPM WITH INTERRUPTS", timeout / tick); + + sc->intr_type = -1; + return ((RD4(sc, off) & mask) == val); + } + + /* If we don't have interrupts poll the device every tick */ + while (timeout > 0) { + if ((RD4(sc, off) & mask) == val) + return (true); + + pause("TPM POLLING", 1); + timeout -= tick; + } + return (false); +} + +static uint16_t +tpmtis_wait_for_burst(struct tpm_sc *sc) +{ + int timeout; + uint16_t burst_count; + + timeout = TPM_TIMEOUT_A; + + while (timeout-- > 0) { + burst_count = (RD4(sc, TPM_STS) & TPM_STS_BURST_MASK) >> + TPM_STS_BURST_OFFSET; + if (burst_count > 0) + break; + + DELAY(1); + } + return (burst_count); +} + +static bool +tpmtis_read_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf) +{ + uint16_t burst_count; + + while (count > 0) { + burst_count = tpmtis_wait_for_burst(sc); + if (burst_count == 0) + return (false); + + burst_count = MIN(burst_count, count); + count -= burst_count; + + while (burst_count-- > 0) + *buf++ = RD1(sc, TPM_DATA_FIFO); + } + + return (true); +} + +static bool +tpmtis_write_bytes(struct tpm_sc *sc, size_t count, uint8_t *buf) +{ + uint16_t burst_count; + + while (count > 0) { + burst_count = tpmtis_wait_for_burst(sc); + if (burst_count == 0) + return (false); + + burst_count = MIN(burst_count, count); + count -= burst_count; + + while (burst_count-- > 0) + WR1(sc, TPM_DATA_FIFO, *buf++); + } + + return (true); +} + + +static bool +tpmtis_request_locality(struct tpm_sc *sc, int locality) +{ + uint8_t mask; + int timeout; + + /* Currently we only support Locality 0 */ + if (locality != 0) + return (false); + + mask = TPM_ACCESS_LOC_ACTIVE | TPM_ACCESS_VALID; + timeout = TPM_TIMEOUT_A; + sc->intr_type = TPM_INT_STS_LOC_CHANGE; + + WR1(sc, TPM_ACCESS, TPM_ACCESS_LOC_REQ); + bus_barrier(sc->mem_res, TPM_ACCESS, 1, BUS_SPACE_BARRIER_WRITE); + if(sc->interrupts) { + tsleep(sc, PWAIT, "TPMLOCREQUEST with INTR", timeout / tick); + return ((RD1(sc, TPM_ACCESS) & mask) == mask); + } else { + while(timeout > 0) { + if ((RD1(sc, TPM_ACCESS) & mask) == mask) + return (true); + + pause("TPMLOCREQUEST POLLING", 1); + timeout -= tick; + } + } + + return (false); +} + +static void +tpmtis_relinquish_locality(struct tpm_sc *sc) +{ + + /* + * Interrupts can only be cleared when a locality is active. + * Clear them now in case interrupt handler didn't make it in time. + */ + if(sc->interrupts) + AND4(sc, TPM_INT_STS, RD4(sc, TPM_INT_STS)); + + OR1(sc, TPM_ACCESS, TPM_ACCESS_LOC_RELINQUISH); +} + +static bool +tpmtis_go_ready(struct tpm_sc *sc) +{ + uint32_t mask; + + mask = TPM_STS_CMD_RDY; + sc->intr_type = TPM_INT_STS_CMD_RDY; + + OR4(sc, TPM_STS, TPM_STS_CMD_RDY); + bus_barrier(sc->mem_res, TPM_STS, 4, BUS_SPACE_BARRIER_WRITE); + if (!tpm_wait_for_u32(sc, TPM_STS, mask, mask, TPM_TIMEOUT_B)) + return (false); + + AND4(sc, TPM_STS, ~TPM_STS_CMD_RDY); + return (true); +} + +static int +tpmtis_transmit(struct tpm_sc *sc, size_t length) +{ + size_t bytes_available; + uint32_t mask, curr_cmd; + int timeout; + + sx_assert(&sc->dev_lock, SA_XLOCKED); + + if (!tpmtis_request_locality(sc, 0)) { + device_printf(sc->dev, + "Failed to obtain locality\n"); + return (EIO); + } + if (!tpmtis_go_ready(sc)) { + device_printf(sc->dev, + "Failed to switch to ready state\n"); + return (EIO); + } + if (!tpmtis_write_bytes(sc, length, sc->buf)) { + device_printf(sc->dev, + "Failed to write cmd to device\n"); + return (EIO); + } + + mask = TPM_STS_VALID; + sc->intr_type = TPM_INT_STS_VALID; + if (!tpm_wait_for_u32(sc, TPM_STS, mask, mask, TPM_TIMEOUT_C)) { + device_printf(sc->dev, + "Timeout while waiting for valid bit\n"); + return (EIO); + } + if (RD4(sc, TPM_STS) & TPM_STS_DATA_EXPECTED) { + device_printf(sc->dev, + "Device expects more data even though we already" + " sent everything we had\n"); + return (EIO); + } + + /* + * Calculate timeout for current command. + * Command code is passed in bytes 6-10. + */ + curr_cmd = be32toh(*(uint32_t *) (&sc->buf[6])); + timeout = tpm20_get_timeout(curr_cmd); + + WR4(sc, TPM_STS, TPM_STS_CMD_START); + bus_barrier(sc->mem_res, TPM_STS, 4, BUS_SPACE_BARRIER_WRITE); + + mask = TPM_STS_DATA_AVAIL | TPM_STS_VALID; + sc->intr_type = TPM_INT_STS_DATA_AVAIL; + if (!tpm_wait_for_u32(sc, TPM_STS, mask, mask, timeout)) { + device_printf(sc->dev, + "Timeout while waiting for device to process cmd\n"); + /* + * Switching to ready state also cancels processing + * current command + */ + if (!tpmtis_go_ready(sc)) + return (EIO); + + /* + * After canceling a command we should get a response, + * check if there is one. + */ + sc->intr_type = TPM_INT_STS_DATA_AVAIL; + if (!tpm_wait_for_u32(sc, TPM_STS, mask, mask, TPM_TIMEOUT_C)) + return (EIO); + } + /* Read response header. Length is passed in bytes 2 - 6. */ + if(!tpmtis_read_bytes(sc, TPM_HEADER_SIZE, sc->buf)) { + device_printf(sc->dev, + "Failed to read response header\n"); + return (EIO); + } + bytes_available = be32toh(*(uint32_t *) (&sc->buf[2])); + + if (bytes_available > TPM_BUFSIZE || bytes_available < TPM_HEADER_SIZE) { + device_printf(sc->dev, + "Incorrect response size: %zu\n", + bytes_available); + return (EIO); + } + if(!tpmtis_read_bytes(sc, bytes_available - TPM_HEADER_SIZE, + &sc->buf[TPM_HEADER_SIZE])) { + device_printf(sc->dev, + "Failed to read response\n"); + return (EIO); + } + tpmtis_relinquish_locality(sc); + sc->pending_data_length = bytes_available; + + return (0); +} + +/* ACPI Driver */ +static device_method_t tpmtis_methods[] = { + DEVMETHOD(device_probe, tpmtis_acpi_probe), + DEVMETHOD(device_attach, tpmtis_attach), + DEVMETHOD(device_detach, tpmtis_detach), + DEVMETHOD(device_shutdown, tpm20_save_state), + DEVMETHOD(device_suspend, tpm20_save_state), + {0, 0} +}; +static driver_t tpmtis_driver = { + "tpmtis", tpmtis_methods, sizeof(struct tpm_sc), +}; + +devclass_t tpmtis_devclass; +DRIVER_MODULE(tpmtis, acpi, tpmtis_driver, tpmtis_devclass, 0, 0);