#include <sys/random.h>
#include <dev/random/randomdev.h>
#include "tpm20.h"
#define TPM_HARVEST_SIZE 16
#define TPM_HARVEST_INTERVAL 10
MALLOC_DEFINE(M_TPM20, "tpm_buffer", "buffer for tpm 2.0 driver");
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
static void tpm20_harvest(void *arg, int unused);
#endif
static int tpm20_restart(device_t dev, bool clear);
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;
struct tpm_priv *priv;
size_t bytes_to_transfer;
size_t offset;
int result = 0;
sc = (struct tpm_sc *)dev->si_drv1;
devfs_get_cdevpriv((void **)&priv);
sx_xlock(&sc->dev_lock);
offset = priv->offset;
bytes_to_transfer = MIN(priv->len, uio->uio_resid);
if (bytes_to_transfer > 0) {
result = uiomove((caddr_t) priv->buf + offset, bytes_to_transfer, uio);
priv->offset += bytes_to_transfer;
priv->len -= bytes_to_transfer;
} else {
result = 0;
}
sx_xunlock(&sc->dev_lock);
return (result);
}
int
tpm20_write(struct cdev *dev, struct uio *uio, int flags)
{
struct tpm_sc *sc;
struct tpm_priv *priv;
size_t byte_count;
int result = 0;
sc = (struct tpm_sc *)dev->si_drv1;
devfs_get_cdevpriv((void **)&priv);
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);
result = uiomove(priv->buf, byte_count, uio);
if (result != 0) {
sx_xunlock(&sc->dev_lock);
return (result);
}
result = TPM_TRANSMIT(sc->dev, priv, byte_count);
sx_xunlock(&sc->dev_lock);
return (result);
}
static struct tpm_priv *
tpm20_priv_alloc(void)
{
struct tpm_priv *priv;
priv = malloc(sizeof (*priv), M_TPM20, M_WAITOK | M_ZERO);
return (priv);
}
static void
tpm20_priv_dtor(void *data)
{
struct tpm_priv *priv = data;
free(priv->buf, M_TPM20);
}
int
tpm20_open(struct cdev *dev, int flag, int mode, struct thread *td)
{
struct tpm_priv *priv;
priv = tpm20_priv_alloc();
devfs_set_cdevpriv(priv, tpm20_priv_dtor);
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);
}
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
static const struct random_source random_tpm = {
.rs_ident = "TPM",
.rs_source = RANDOM_PURE_TPM,
};
#endif
int
tpm20_init(struct tpm_sc *sc)
{
struct make_dev_args args;
int result;
sc->internal_priv = tpm20_priv_alloc();
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);
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
random_source_register(&random_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)
{
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
if (device_is_attached(sc->dev))
taskqueue_drain_timeout(taskqueue_thread, &sc->harvest_task);
random_source_deregister(&random_tpm);
#endif
tpm20_priv_dtor(sc->internal_priv);
sx_destroy(&sc->dev_lock);
if (sc->sc_cdev != NULL)
destroy_dev(sc->sc_cdev);
}
int
tpm20_resume(device_t dev)
{
tpm20_restart(dev, false);
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
struct tpm_sc *sc;
sc = device_get_softc(dev);
taskqueue_enqueue_timeout(taskqueue_thread, &sc->harvest_task,
hz * TPM_HARVEST_INTERVAL);
#endif
return (0);
}
int
tpm20_suspend(device_t dev)
{
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
struct tpm_sc *sc;
sc = device_get_softc(dev);
taskqueue_drain_timeout(taskqueue_thread, &sc->harvest_task);
#endif
return (tpm20_save_state(dev, true));
}
int
tpm20_shutdown(device_t dev)
{
return (tpm20_save_state(dev, false));
}
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
static void
tpm20_harvest(void *arg, int unused)
{
struct tpm_sc *sc;
struct tpm_priv *priv;
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;
sx_xlock(&sc->dev_lock);
priv = sc->internal_priv;
memcpy(priv->buf, cmd, sizeof(cmd));
result = TPM_TRANSMIT(sc->dev, priv, sizeof(cmd));
if (result != 0) {
sx_xunlock(&sc->dev_lock);
return;
}
entropy_size = (uint16_t) priv->buf[TPM_HEADER_SIZE + 1];
if (entropy_size > 0) {
entropy_size = MIN(entropy_size, TPM_HARVEST_SIZE);
memcpy(entropy,
priv->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
static int
tpm20_restart(device_t dev, bool clear)
{
struct tpm_sc *sc;
struct tpm_priv *priv;
uint8_t startup_cmd[] = {
0x80, 0x01,
0x00, 0x00, 0x00, 0x0C,
0x00, 0x00, 0x01, 0x44,
0x00, 0x01
};
sc = device_get_softc(dev);
if (clear)
startup_cmd[11] = 0;
if (sc == NULL)
return (0);
sx_xlock(&sc->dev_lock);
priv = sc->internal_priv;
memcpy(priv->buf, startup_cmd, sizeof(startup_cmd));
TPM_TRANSMIT(sc->dev, priv, sizeof(startup_cmd));
sx_xunlock(&sc->dev_lock);
return (0);
}
static int
tpm20_save_state(device_t dev, bool suspend)
{
struct tpm_sc *sc;
struct tpm_priv *priv;
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)
return (0);
sx_xlock(&sc->dev_lock);
priv = sc->internal_priv;
memcpy(priv->buf, save_cmd, sizeof(save_cmd));
TPM_TRANSMIT(sc->dev, priv, 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;
}