#include <sys/random.h>
#include "tpm20.h"
#define TPM_HARVEST_SIZE 16
#define TPM_HARVEST_INTERVAL 10000000
MALLOC_DECLARE(M_TPM20);
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);
#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 dev_ops tpm20_ops = {
{ "tpm20", 0, 0 },
.d_open = tpm20_open,
.d_close = tpm20_close,
.d_read = tpm20_read,
.d_write = tpm20_write,
.d_ioctl = tpm20_ioctl,
};
int
tpm20_read(struct dev_read_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct uio *uio = ap->a_uio;
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);
lockmgr(&sc->dev_lock, LK_EXCLUSIVE);
if (sc->owner_tid != uio->uio_td) {
lockmgr(&sc->dev_lock, LK_RELEASE);
return (EPERM);
}
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;
}
lockmgr(&sc->dev_lock, LK_RELEASE);
return (result);
}
int
tpm20_write(struct dev_write_args *ap)
{
cdev_t dev = ap->a_head.a_dev;
struct uio *uio = ap->a_uio;
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);
}
lockmgr(&sc->dev_lock, LK_EXCLUSIVE);
while (sc->pending_data_length != 0)
cv_wait(&sc->buf_cv, &sc->dev_lock);
result = uiomove(sc->buf, byte_count, uio);
if (result != 0) {
lockmgr(&sc->dev_lock, LK_RELEASE);
return (result);
}
result = sc->transmit(sc, byte_count);
if (result == 0) {
callout_reset(&sc->discard_buffer_callout,
TPM_READ_TIMEOUT / ustick, tpm20_discard_buffer, sc);
sc->owner_tid = uio->uio_td;
}
lockmgr(&sc->dev_lock, LK_RELEASE);
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;
lockmgr(&sc->dev_lock, LK_EXCLUSIVE);
memset(sc->buf, 0, TPM_BUFSIZE);
sc->pending_data_length = 0;
cv_signal(&sc->buf_cv);
lockmgr(&sc->dev_lock, LK_RELEASE);
device_printf(sc->dev,
"User failed to read buffer in time\n");
}
int
tpm20_open(struct dev_open_args *ap)
{
return (0);
}
int
tpm20_close(struct dev_close_args *ap)
{
return (0);
}
int
tpm20_ioctl(struct dev_ioctl_args *ap)
{
return (ENOTTY);
}
int
tpm20_init(struct tpm_sc *sc)
{
sc->buf = kmalloc(TPM_BUFSIZE, M_TPM20, M_WAITOK);
lockinit(&sc->dev_lock, "TPM driver lock", 0, LK_CANRECURSE);
cv_init(&sc->buf_cv, "TPM buffer cv");
callout_init_mp(&sc->discard_buffer_callout);
#ifdef TPM_HARVEST
sc->harvest_ticks = TPM_HARVEST_INTERVAL / ustick;
callout_init_mp(&sc->harvest_callout);
callout_reset(&sc->harvest_callout, 0, tpm20_harvest, sc);
#endif
sc->pending_data_length = 0;
sc->sc_cdev = make_dev(&tpm20_ops, device_get_unit(sc->dev),
UID_ROOT, GID_WHEEL, TPM_CDEV_PERM_FLAG, TPM_CDEV_NAME);
sc->sc_cdev->si_drv1 = sc;
return (0);
}
void
tpm20_release(struct tpm_sc *sc)
{
#ifdef TPM_HARVEST
callout_drain(&sc->harvest_callout);
#endif
if (sc->buf != NULL)
kfree(sc->buf, M_TPM20);
lockuninit(&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
static void
tpm20_harvest(void *arg)
{
struct tpm_sc *sc;
unsigned char entropy[TPM_HARVEST_SIZE];
uint16_t entropy_size;
int result;
uint8_t cmd[] = {
0x80, 0x01,
0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x01, 0x7b,
0x00, TPM_HARVEST_SIZE
};
sc = arg;
lockmgr(&sc->dev_lock, LK_EXCLUSIVE);
while (sc->pending_data_length != 0)
cv_wait(&sc->buf_cv, &sc->dev_lock);
memcpy(sc->buf, cmd, sizeof(cmd));
result = sc->transmit(sc, sizeof(cmd));
if (result != 0) {
lockmgr(&sc->dev_lock, LK_RELEASE);
return;
}
sc->pending_data_length = 0;
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);
}
lockmgr(&sc->dev_lock, LK_RELEASE);
if (entropy_size > 0)
add_buffer_randomness_src(entropy, entropy_size, RAND_SRC_TPM);
callout_reset(&sc->harvest_callout, sc->harvest_ticks, tpm20_harvest, sc);
}
#endif
static int
tpm20_save_state(device_t dev, bool suspend)
{
struct tpm_sc *sc;
uint8_t save_cmd[] = {
0x80, 0x01,
0x00, 0x00, 0x00, 0x0C,
0x00, 0x00, 0x01, 0x45,
0x00, 0x00
};
sc = device_get_softc(dev);
if (suspend)
save_cmd[11] = 1;
if (sc == NULL || sc->buf == NULL)
return (0);
lockmgr(&sc->dev_lock, LK_EXCLUSIVE);
memcpy(sc->buf, save_cmd, sizeof(save_cmd));
sc->transmit(sc, sizeof(save_cmd));
lockmgr(&sc->dev_lock, LK_RELEASE);
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;
}