freebsd-dev/sys/dev/tpm/tpm20.c

261 lines
5.7 KiB
C
Raw Normal View History

/*-
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#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);
static int tpm20_save_state(device_t dev, bool suspend);
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_suspend(device_t dev)
{
return (tpm20_save_state(dev, true));
}
int
tpm20_shutdown(device_t dev)
{
return (tpm20_save_state(dev, false));
}
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, 0x00, 0x00 /* cmd TPM_CC_Shutdown */
};
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));
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;
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;
}