/*- * 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 #include #include "tpm20.h" #define TPM_HARVEST_SIZE 16 /* * Perform a harvest every 10 seconds. * Since discrete TPMs are painfully slow * we don't want to execute this too often * as the chip is likely to be used by others too. */ #define TPM_HARVEST_INTERVAL 10 MALLOC_DEFINE(M_TPM20, "tpm_buffer", "buffer for tpm 2.0 driver"); static void tpm20_discard_buffer(void *arg); #ifdef TPM_HARVEST static void tpm20_harvest(void *arg, int unused); #endif static int tpm20_save_state(device_t dev, bool suspend); static d_open_t tpm20_open; static d_close_t tpm20_close; static d_read_t tpm20_read; static d_write_t tpm20_write; static d_ioctl_t tpm20_ioctl; static struct cdevsw tpm20_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", }; int tpm20_read(struct cdev *dev, struct uio *uio, int flags) { struct tpm_sc *sc; size_t bytes_to_transfer; size_t offset; int result = 0; sc = (struct tpm_sc *)dev->si_drv1; callout_stop(&sc->discard_buffer_callout); sx_xlock(&sc->dev_lock); if (sc->owner_tid != uio->uio_td->td_tid) { sx_xunlock(&sc->dev_lock); return (EPERM); } bytes_to_transfer = MIN(sc->pending_data_length, uio->uio_resid); offset = sc->total_length - sc->pending_data_length; if (bytes_to_transfer > 0) { result = uiomove((caddr_t) sc->buf + offset, bytes_to_transfer, uio); sc->pending_data_length -= bytes_to_transfer; 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 = TPM_TRANSMIT(sc->dev, byte_count); if (result == 0) { callout_reset(&sc->discard_buffer_callout, TPM_READ_TIMEOUT / tick, tpm20_discard_buffer, sc); sc->owner_tid = uio->uio_td->td_tid; } 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; sc->total_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; cv_init(&sc->buf_cv, "TPM buffer cv"); callout_init(&sc->discard_buffer_callout, 1); sc->pending_data_length = 0; sc->total_length = 0; make_dev_args_init(&args); args.mda_devsw = &tpm20_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); #ifdef TPM_HARVEST random_harvest_register_source(RANDOM_PURE_TPM); TIMEOUT_TASK_INIT(taskqueue_thread, &sc->harvest_task, 0, tpm20_harvest, sc); taskqueue_enqueue_timeout(taskqueue_thread, &sc->harvest_task, 0); #endif return (result); } void tpm20_release(struct tpm_sc *sc) { #ifdef TPM_HARVEST if (device_is_attached(sc->dev)) taskqueue_drain_timeout(taskqueue_thread, &sc->harvest_task); random_harvest_deregister_source(RANDOM_PURE_TPM); #endif 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_suspend(device_t dev) { return (tpm20_save_state(dev, true)); } int tpm20_shutdown(device_t dev) { return (tpm20_save_state(dev, false)); } #ifdef TPM_HARVEST /* * Get TPM_HARVEST_SIZE random bytes and add them * into system entropy pool. */ static void tpm20_harvest(void *arg, int unused) { struct tpm_sc *sc; unsigned char entropy[TPM_HARVEST_SIZE]; uint16_t entropy_size; int result; uint8_t cmd[] = { 0x80, 0x01, /* TPM_ST_NO_SESSIONS tag*/ 0x00, 0x00, 0x00, 0x0c, /* cmd length */ 0x00, 0x00, 0x01, 0x7b, /* cmd TPM_CC_GetRandom */ 0x00, TPM_HARVEST_SIZE /* number of bytes requested */ }; sc = arg; sx_xlock(&sc->dev_lock); while (sc->pending_data_length != 0) cv_wait(&sc->buf_cv, &sc->dev_lock); memcpy(sc->buf, cmd, sizeof(cmd)); result = TPM_TRANSMIT(sc->dev, sizeof(cmd)); if (result != 0) { sx_xunlock(&sc->dev_lock); return; } /* Ignore response size */ sc->pending_data_length = 0; sc->total_length = 0; /* The number of random bytes we got is placed right after the header */ entropy_size = (uint16_t) sc->buf[TPM_HEADER_SIZE + 1]; if (entropy_size > 0) { entropy_size = MIN(entropy_size, TPM_HARVEST_SIZE); memcpy(entropy, sc->buf + TPM_HEADER_SIZE + sizeof(uint16_t), entropy_size); } sx_xunlock(&sc->dev_lock); if (entropy_size > 0) random_harvest_queue(entropy, entropy_size, RANDOM_PURE_TPM); taskqueue_enqueue_timeout(taskqueue_thread, &sc->harvest_task, hz * TPM_HARVEST_INTERVAL); } #endif /* TPM_HARVEST */ static int tpm20_save_state(device_t dev, bool suspend) { 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, /* cmd TPM_CC_Shutdown */ 0x00, 0x00 /* TPM_SU_STATE */ }; sc = device_get_softc(dev); /* * Inform the TPM whether we are going to suspend or reboot/shutdown. */ if (suspend) save_cmd[11] = 1; /* TPM_SU_STATE */ if (sc == NULL || sc->buf == NULL) return (0); sx_xlock(&sc->dev_lock); memcpy(sc->buf, save_cmd, sizeof(save_cmd)); TPM_TRANSMIT(sc->dev, 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; case TPM_CC_SequenceComplete: case TPM_CC_Startup: case TPM_CC_SequenceUpdate: case TPM_CC_GetCapability: case TPM_CC_PCR_Extend: case TPM_CC_EventSequenceComplete: case TPM_CC_HashSequenceStart: timeout = TPM_TIMEOUT_C; break; default: timeout = TPM_TIMEOUT_B; break; } return timeout; }