#include <sys/devops.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/byteorder.h>
#ifdef sun4v
#include <sys/hypervisor_api.h>
#include <sys/hsvc.h>
#endif
#include <tss/platform.h>
#include <tss/tpm.h>
#include "tpm_tis.h"
#include "tpm_ddi.h"
#include "tpm_duration.h"
#define TPM_HEADER_SIZE 10
typedef enum {
TPM_TAG_OFFSET = 0,
TPM_PARAMSIZE_OFFSET = 2,
TPM_RETURN_OFFSET = 6,
TPM_COMMAND_CODE_OFFSET = 6,
} TPM_HEADER_OFFSET_T;
#define DEFAULT_SHORT_DURATION 750000
#define DEFAULT_MEDIUM_DURATION 1000000
#define DEFAULT_LONG_DURATION 300000000
#define DEFAULT_TIMEOUT_A 750000
#define DEFAULT_TIMEOUT_B 2000000
#define DEFAULT_TIMEOUT_C 750000
#define DEFAULT_TIMEOUT_D 750000
#define TEN_MILLISECONDS 10000
#define FOUR_HUNDRED_MILLISECONDS 400000
#define DEFAULT_LOCALITY 0
typedef enum {
TPM_CAP_RESPSIZE_OFFSET = 10,
TPM_CAP_RESP_OFFSET = 14,
} TPM_CAP_RET_OFFSET_T;
typedef enum {
TPM_CAP_TIMEOUT_A_OFFSET = 14,
TPM_CAP_TIMEOUT_B_OFFSET = 18,
TPM_CAP_TIMEOUT_C_OFFSET = 22,
TPM_CAP_TIMEOUT_D_OFFSET = 26,
} TPM_CAP_TIMEOUT_OFFSET_T;
typedef enum {
TPM_CAP_DUR_SHORT_OFFSET = 14,
TPM_CAP_DUR_MEDIUM_OFFSET = 18,
TPM_CAP_DUR_LONG_OFFSET = 22,
} TPM_CAP_DURATION_OFFSET_T;
#define TPM_CAP_VERSION_INFO_OFFSET 14
#define TPM_CAP_VERSION_INFO_SIZE 15
static int itpm_command(tpm_state_t *tpm, uint8_t *buf, size_t bufsiz);
static int tpm_get_timeouts(tpm_state_t *tpm);
static int tpm_get_duration(tpm_state_t *tpm);
static int tpm_get_version(tpm_state_t *tpm);
static int tpm_continue_selftest(tpm_state_t *tpm);
static int tpm_wait_for_stat(tpm_state_t *, uint8_t, clock_t);
static clock_t tpm_get_ordinal_duration(tpm_state_t *, uint8_t);
static int tis_check_active_locality(tpm_state_t *, char);
static int tis_request_locality(tpm_state_t *, char);
static void tis_release_locality(tpm_state_t *, char, int);
static int tis_init(tpm_state_t *);
static uint8_t tis_get_status(tpm_state_t *);
static int tis_send_data(tpm_state_t *, uint8_t *, size_t);
static int tis_recv_data(tpm_state_t *, uint8_t *, size_t);
static int receive_data(tpm_state_t *, uint8_t *, size_t);
static inline int tpm_io_lock(tpm_state_t *);
static inline void tpm_unlock(tpm_state_t *);
static void tpm_cleanup(dev_info_t *, tpm_state_t *);
static int tpm_attach(dev_info_t *, ddi_attach_cmd_t);
static int tpm_detach(dev_info_t *, ddi_detach_cmd_t);
static int tpm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int tpm_quiesce(dev_info_t *);
static int tpm_open(dev_t *, int, int, cred_t *);
static int tpm_close(dev_t, int, int, cred_t *);
static int tpm_read(dev_t, struct uio *, cred_t *);
static int tpm_write(dev_t, struct uio *, cred_t *);
static struct cb_ops tpm_cb_ops = {
tpm_open,
tpm_close,
nodev,
nodev,
nodev,
tpm_read,
tpm_write,
nodev,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_MP,
CB_REV,
nodev,
nodev
};
static struct dev_ops tpm_dev_ops = {
DEVO_REV,
0,
tpm_getinfo,
nulldev,
nulldev,
tpm_attach,
tpm_detach,
nodev,
&tpm_cb_ops,
(struct bus_ops *)NULL,
nodev,
tpm_quiesce
};
static struct modldrv modldrv = {
&mod_driverops,
"TPM 1.2 driver",
&tpm_dev_ops
};
static struct modlinkage tpm_ml = {
MODREV_1,
&modldrv,
NULL
};
#ifdef KCF_TPM_RNG_PROVIDER
#define IDENT_TPMRNG "TPM Random Number Generator"
#include <sys/crypto/common.h>
#include <sys/crypto/impl.h>
#include <sys/crypto/spi.h>
static void tpmrng_provider_status(crypto_provider_handle_t, uint_t *);
static crypto_control_ops_t tpmrng_control_ops = {
tpmrng_provider_status
};
static int tpmrng_seed_random(crypto_provider_handle_t, crypto_session_id_t,
uchar_t *, size_t, uint_t, uint32_t, crypto_req_handle_t);
static int tpmrng_generate_random(crypto_provider_handle_t,
crypto_session_id_t, uchar_t *, size_t, crypto_req_handle_t);
static crypto_random_number_ops_t tpmrng_random_number_ops = {
tpmrng_seed_random,
tpmrng_generate_random
};
static int tpmrng_ext_info(crypto_provider_handle_t,
crypto_provider_ext_info_t *,
crypto_req_handle_t);
static crypto_provider_management_ops_t tpmrng_extinfo_op = {
tpmrng_ext_info,
NULL,
NULL,
NULL
};
static int tpmrng_register(tpm_state_t *);
static int tpmrng_unregister(tpm_state_t *);
static crypto_ops_t tpmrng_crypto_ops = {
&tpmrng_control_ops,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
&tpmrng_random_number_ops,
NULL,
NULL,
NULL,
&tpmrng_extinfo_op,
NULL,
NULL
};
static crypto_provider_info_t tpmrng_prov_info = {
CRYPTO_SPI_VERSION_2,
"TPM Random Number Provider",
CRYPTO_HW_PROVIDER,
NULL,
NULL,
&tpmrng_crypto_ops,
0,
NULL,
0,
NULL
};
#endif
static void *statep = NULL;
#define TPM_EXCLUSIVE_LOCK(tpm) { \
mutex_enter(&tpm->pm_mutex); \
while (tpm->suspended) \
cv_wait(&tpm->suspend_cv, &tpm->pm_mutex); \
mutex_exit(&tpm->pm_mutex); }
#ifdef sun4v
extern uint64_t
hcall_tpm_get(uint64_t, uint64_t, uint64_t, uint64_t *);
extern uint64_t
hcall_tpm_put(uint64_t, uint64_t, uint64_t, uint64_t);
static inline uint8_t
tpm_get8(tpm_state_t *tpm, unsigned long offset)
{
uint64_t value;
ASSERT(tpm != NULL);
(void) hcall_tpm_get(tpm->locality, offset, sizeof (uint8_t), &value);
return ((uint8_t)value);
}
static inline uint32_t
tpm_get32(tpm_state_t *tpm, unsigned long offset)
{
uint64_t value;
ASSERT(tpm != NULL);
(void) hcall_tpm_get(tpm->locality, offset, sizeof (uint32_t), &value);
return ((uint32_t)value);
}
static inline void
tpm_put8(tpm_state_t *tpm, unsigned long offset, uint8_t value)
{
ASSERT(tpm != NULL);
(void) hcall_tpm_put(tpm->locality, offset, sizeof (uint8_t), value);
}
#else
static inline uint8_t
tpm_get8(tpm_state_t *tpm, unsigned long offset)
{
ASSERT(tpm != NULL);
return (ddi_get8(tpm->handle,
(uint8_t *)(TPM_LOCALITY_OFFSET(tpm->locality) |
(uintptr_t)tpm->addr + offset)));
}
static inline uint32_t
tpm_get32(tpm_state_t *tpm, unsigned long offset)
{
ASSERT(tpm != NULL);
return (ddi_get32(tpm->handle,
(uint32_t *)(TPM_LOCALITY_OFFSET(tpm->locality) |
(uintptr_t)tpm->addr + offset)));
}
static inline void
tpm_put8(tpm_state_t *tpm, unsigned long offset, uint8_t value)
{
ASSERT(tpm != NULL);
ddi_put8(tpm->handle,
(uint8_t *)(TPM_LOCALITY_OFFSET(tpm->locality) |
(uintptr_t)tpm->addr + offset), value);
}
#endif
static int
tpm_quiesce(dev_info_t *dip)
{
return (DDI_SUCCESS);
}
static uint32_t
load32(uchar_t *ptr, uint32_t offset)
{
uint32_t val;
bcopy(ptr + offset, &val, sizeof (uint32_t));
return (ntohl(val));
}
static int
tpm_get_timeouts(tpm_state_t *tpm)
{
int ret;
uint32_t timeout;
uint32_t len;
uint8_t buf[30] = {
0, 193,
0, 0, 0, 22,
0, 0, 0, 101,
0, 0, 0, 5,
0, 0, 0, 4,
0, 0, 1, 21
};
ASSERT(tpm != NULL);
ret = itpm_command(tpm, buf, sizeof (buf));
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: itpm_command failed", __func__);
#endif
return (DDI_FAILURE);
}
len = load32(buf, TPM_CAP_RESPSIZE_OFFSET);
if (len != 4 * sizeof (uint32_t)) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: capability response size should be %d"
"instead len = %d",
__func__, (int)(4 * sizeof (uint32_t)), (int)len);
#endif
return (DDI_FAILURE);
}
timeout = load32(buf, TPM_CAP_TIMEOUT_A_OFFSET);
if (timeout == 0) {
timeout = DEFAULT_TIMEOUT_A;
} else if (timeout < TEN_MILLISECONDS) {
timeout *= 1000;
}
tpm->timeout_a = drv_usectohz(timeout);
timeout = load32(buf, TPM_CAP_TIMEOUT_B_OFFSET);
if (timeout == 0) {
timeout = DEFAULT_TIMEOUT_B;
} else if (timeout < TEN_MILLISECONDS) {
timeout *= 1000;
}
tpm->timeout_b = drv_usectohz(timeout);
timeout = load32(buf, TPM_CAP_TIMEOUT_C_OFFSET);
if (timeout == 0) {
timeout = DEFAULT_TIMEOUT_C;
} else if (timeout < TEN_MILLISECONDS) {
timeout *= 1000;
}
tpm->timeout_c = drv_usectohz(timeout);
timeout = load32(buf, TPM_CAP_TIMEOUT_D_OFFSET);
if (timeout == 0) {
timeout = DEFAULT_TIMEOUT_D;
} else if (timeout < TEN_MILLISECONDS) {
timeout *= 1000;
}
tpm->timeout_d = drv_usectohz(timeout);
return (DDI_SUCCESS);
}
static int
tpm_get_duration(tpm_state_t *tpm)
{
int ret;
uint32_t duration;
uint32_t len;
uint8_t buf[30] = {
0, 193,
0, 0, 0, 22,
0, 0, 0, 101,
0, 0, 0, 5,
0, 0, 0, 4,
0, 0, 1, 32
};
ASSERT(tpm != NULL);
ret = itpm_command(tpm, buf, sizeof (buf));
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: itpm_command failed with ret code: 0x%x",
__func__, ret);
#endif
return (DDI_FAILURE);
}
len = load32(buf, TPM_CAP_RESPSIZE_OFFSET);
if (len != 3 * sizeof (uint32_t)) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: capability response should be %d, "
"instead, it's %d",
__func__, (int)(3 * sizeof (uint32_t)), (int)len);
#endif
return (DDI_FAILURE);
}
duration = load32(buf, TPM_CAP_DUR_SHORT_OFFSET);
if (duration == 0) {
duration = DEFAULT_SHORT_DURATION;
} else if (duration < TEN_MILLISECONDS) {
duration *= 1000;
}
tpm->duration[TPM_SHORT] = drv_usectohz(duration);
duration = load32(buf, TPM_CAP_DUR_MEDIUM_OFFSET);
if (duration == 0) {
duration = DEFAULT_MEDIUM_DURATION;
} else if (duration < TEN_MILLISECONDS) {
duration *= 1000;
}
tpm->duration[TPM_MEDIUM] = drv_usectohz(duration);
duration = load32(buf, TPM_CAP_DUR_LONG_OFFSET);
if (duration == 0) {
duration = DEFAULT_LONG_DURATION;
} else if (duration < FOUR_HUNDRED_MILLISECONDS) {
duration *= 1000;
}
tpm->duration[TPM_LONG] = drv_usectohz(duration);
tpm->duration[TPM_UNDEFINED] = tpm->duration[TPM_LONG];
return (DDI_SUCCESS);
}
static int
tpm_get_version(tpm_state_t *tpm)
{
int ret;
uint32_t len;
char vendorId[5];
uint8_t buf[64] = {
0, 193,
0, 0, 0, 18,
0, 0, 0, 101,
0, 0, 0, 0x1A,
0, 0, 0, 0,
};
ASSERT(tpm != NULL);
ret = itpm_command(tpm, buf, sizeof (buf));
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: itpm_command failed with ret code: 0x%x",
__func__, ret);
#endif
return (DDI_FAILURE);
}
len = load32(buf, TPM_CAP_RESPSIZE_OFFSET);
if (len < TPM_CAP_VERSION_INFO_SIZE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: capability response should be greater"
" than %d, instead, it's %d",
__func__, TPM_CAP_VERSION_INFO_SIZE, len);
#endif
return (DDI_FAILURE);
}
bcopy(buf + TPM_CAP_VERSION_INFO_OFFSET, &tpm->vers_info,
TPM_CAP_VERSION_INFO_SIZE);
bcopy(tpm->vers_info.tpmVendorID, vendorId,
sizeof (tpm->vers_info.tpmVendorID));
vendorId[4] = '\0';
cmn_err(CE_NOTE, "!TPM found: Ver %d.%d, Rev %d.%d, "
"SpecLevel %d, errataRev %d, VendorId '%s'",
tpm->vers_info.version.major,
tpm->vers_info.version.minor,
tpm->vers_info.version.revMajor,
tpm->vers_info.version.revMinor,
(int)ntohs(tpm->vers_info.specLevel),
tpm->vers_info.errataRev,
vendorId);
if (tpm->vers_info.version.major != 1 &&
tpm->vers_info.version.minor != 2) {
cmn_err(CE_WARN, "!%s: Unsupported TPM version (%d.%d)",
__func__,
tpm->vers_info.version.major,
tpm->vers_info.version.minor);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
tpm_continue_selftest(tpm_state_t *tpm)
{
int ret;
uint8_t buf[10] = {
0, 193,
0, 0, 0, 10,
0, 0, 0, 83
};
ret = itpm_command(tpm, buf, sizeof (buf));
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: itpm_command failed", __func__);
#endif
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static clock_t
tpm_get_ordinal_duration(tpm_state_t *tpm, uint8_t ordinal)
{
uint8_t index;
ASSERT(tpm != NULL);
if (ordinal & TSC_ORDINAL_MASK) {
if (ordinal >= TSC_ORDINAL_MAX) {
#ifdef DEBUG
cmn_err(CE_WARN,
"!%s: tsc ordinal: %d exceeds MAX: %d",
__func__, ordinal, TSC_ORDINAL_MAX);
#endif
return (0);
}
index = tsc_ords_duration[ordinal];
} else {
if (ordinal >= TPM_ORDINAL_MAX) {
#ifdef DEBUG
cmn_err(CE_WARN,
"!%s: ordinal %d exceeds MAX: %d",
__func__, ordinal, TPM_ORDINAL_MAX);
#endif
return (0);
}
index = tpm_ords_duration[ordinal];
}
if (index > TPM_DURATION_MAX_IDX) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: duration index '%d' is out of bounds",
__func__, index);
#endif
return (0);
}
return (tpm->duration[index]);
}
static int
itpm_command(tpm_state_t *tpm, uint8_t *buf, size_t bufsiz)
{
int ret;
uint32_t count;
ASSERT(tpm != NULL && buf != NULL);
count = load32(buf, TPM_PARAMSIZE_OFFSET);
if (count == 0 || (count > bufsiz)) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: invalid byte count value "
"(%d > bufsiz %d)", __func__, (int)count, (int)bufsiz);
#endif
return (DDI_FAILURE);
}
ret = tis_send_data(tpm, buf, count);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_send_data failed with error %x",
__func__, ret);
#endif
return (DDI_FAILURE);
}
ret = tis_recv_data(tpm, buf, bufsiz);
if (ret < TPM_HEADER_SIZE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_recv_data failed", __func__);
#endif
return (DDI_FAILURE);
}
ret = load32(buf, TPM_RETURN_OFFSET);
if (ret != TPM_SUCCESS) {
if (ret == TPM_E_DEACTIVATED)
cmn_err(CE_WARN, "!%s: TPM is deactivated", __func__);
else if (ret == TPM_E_DISABLED)
cmn_err(CE_WARN, "!%s: TPM is disabled", __func__);
else
cmn_err(CE_WARN, "!%s: TPM error code 0x%0x",
__func__, ret);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static uint16_t
tpm_get_burstcount(tpm_state_t *tpm)
{
clock_t stop;
uint16_t burstcnt;
ASSERT(tpm != NULL);
stop = ddi_get_lbolt() + tpm->timeout_d;
do {
burstcnt = tpm_get8(tpm, TPM_STS + 1);
burstcnt += tpm_get8(tpm, TPM_STS + 2) << 8;
if (burstcnt)
return (burstcnt);
delay(tpm->timeout_poll);
} while (ddi_get_lbolt() < stop);
return (0);
}
static void
tpm_set_ready(tpm_state_t *tpm)
{
tpm_put8(tpm, TPM_STS, TPM_STS_CMD_READY);
}
static int
receive_data(tpm_state_t *tpm, uint8_t *buf, size_t bufsiz)
{
int size = 0;
int retried = 0;
uint8_t stsbits;
uint16_t burstcnt;
ASSERT(tpm != NULL && buf != NULL);
retry:
while (size < bufsiz && (tpm_wait_for_stat(tpm,
(TPM_STS_DATA_AVAIL|TPM_STS_VALID),
tpm->timeout_c) == DDI_SUCCESS)) {
burstcnt = tpm_get_burstcount(tpm);
for (; burstcnt > 0 && size < bufsiz; burstcnt--) {
buf[size++] = tpm_get8(tpm, TPM_DATA_FIFO);
}
}
stsbits = tis_get_status(tpm);
if (size < bufsiz && !(stsbits & TPM_STS_DATA_AVAIL) && retried == 0) {
tpm_put8(tpm, TPM_STS, TPM_STS_RESPONSE_RETRY);
retried++;
size = 0;
goto retry;
}
return (size);
}
static int
tis_recv_data(tpm_state_t *tpm, uint8_t *buf, size_t bufsiz)
{
int ret;
int size = 0;
uint32_t expected, status;
uint32_t cmdresult;
ASSERT(tpm != NULL && buf != NULL);
if (bufsiz < TPM_HEADER_SIZE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: received data should contain at least "
"the header which is %d bytes long",
__func__, TPM_HEADER_SIZE);
#endif
goto OUT;
}
size = receive_data(tpm, buf, TPM_HEADER_SIZE);
if (size < TPM_HEADER_SIZE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: recv TPM_HEADER failed, size = %d",
__func__, size);
#endif
goto OUT;
}
cmdresult = load32(buf, TPM_RETURN_OFFSET);
expected = load32(buf, TPM_PARAMSIZE_OFFSET);
if (expected > bufsiz) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: paramSize is bigger "
"than the requested size: paramSize=%d bufsiz=%d result=%d",
__func__, (int)expected, (int)bufsiz, cmdresult);
#endif
goto OUT;
}
size += receive_data(tpm, (uint8_t *)&buf[TPM_HEADER_SIZE],
expected - TPM_HEADER_SIZE);
if (size < expected) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: received data length (%d) "
"is less than expected (%d)", __func__, size, expected);
#endif
goto OUT;
}
ret = tpm_wait_for_stat(tpm, TPM_STS_VALID, tpm->timeout_c);
status = tis_get_status(tpm);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: TPM didn't set stsValid after its I/O: "
"status = 0x%08X", __func__, status);
#endif
goto OUT;
}
if (status & TPM_STS_DATA_AVAIL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: TPM_STS_DATA_AVAIL is set:0x%08X",
__func__, status);
#endif
goto OUT;
}
tis_release_locality(tpm, tpm->locality, 0);
OUT:
tpm_set_ready(tpm);
tis_release_locality(tpm, tpm->locality, 0);
return (size);
}
static int
tis_send_data(tpm_state_t *tpm, uint8_t *buf, size_t bufsiz)
{
int ret;
uint8_t status;
uint16_t burstcnt;
uint32_t ordinal;
size_t count = 0;
ASSERT(tpm != NULL && buf != NULL);
if (bufsiz == 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: bufsiz arg is zero", __func__);
#endif
return (DDI_FAILURE);
}
status = tis_get_status(tpm);
if (!(status & TPM_STS_CMD_READY)) {
tpm_set_ready(tpm);
ret = tpm_wait_for_stat(tpm, TPM_STS_CMD_READY, tpm->timeout_b);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: could not put the TPM "
"in the command ready state:"
"tpm_wait_for_stat returned error",
__func__);
#endif
goto FAIL;
}
}
while (count < bufsiz - 1) {
burstcnt = tpm_get_burstcount(tpm);
if (burstcnt == 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tpm_get_burstcnt returned error",
__func__);
#endif
ret = DDI_FAILURE;
goto FAIL;
}
for (; burstcnt > 0 && count < bufsiz - 1; burstcnt--) {
tpm_put8(tpm, TPM_DATA_FIFO, buf[count]);
count++;
}
ret = tpm_wait_for_stat(tpm,
(TPM_STS_VALID | TPM_STS_DATA_EXPECT), tpm->timeout_c);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: TPM didn't enter STS_VALID "
"state", __func__);
#endif
goto FAIL;
}
}
tpm_put8(tpm, TPM_DATA_FIFO, buf[count]);
count++;
ret = tpm_wait_for_stat(tpm, TPM_STS_VALID, tpm->timeout_c);
if (ret == DDI_FAILURE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tpm didn't enter STS_VALID state",
__func__);
#endif
goto FAIL;
}
status = tis_get_status(tpm);
if ((status & TPM_STS_DATA_EXPECT) != 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: DATA_EXPECT should not be set after "
"writing the last byte: status=0x%08X", __func__, status);
#endif
ret = DDI_FAILURE;
goto FAIL;
}
tpm_put8(tpm, TPM_STS, TPM_STS_GO);
ordinal = load32(buf, TPM_COMMAND_CODE_OFFSET);
ret = tpm_wait_for_stat(tpm, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
tpm_get_ordinal_duration(tpm, ordinal));
if (ret == DDI_FAILURE) {
#ifdef DEBUG
status = tis_get_status(tpm);
if (!(status & TPM_STS_DATA_AVAIL) ||
!(status & TPM_STS_VALID)) {
cmn_err(CE_WARN, "!%s: TPM not ready or valid "
"(ordinal = %d timeout = %ld status = 0x%0x)",
__func__, ordinal,
tpm_get_ordinal_duration(tpm, ordinal),
status);
} else {
cmn_err(CE_WARN, "!%s: tpm_wait_for_stat "
"(DATA_AVAIL | VALID) failed status = 0x%0X",
__func__, status);
}
#endif
goto FAIL;
}
return (DDI_SUCCESS);
FAIL:
tpm_set_ready(tpm);
tis_release_locality(tpm, tpm->locality, 0);
return (ret);
}
static void
tis_release_locality(tpm_state_t *tpm, char locality, int force)
{
ASSERT(tpm != NULL && locality >= 0 && locality < 5);
if (force ||
(tpm_get8(tpm, TPM_ACCESS) &
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) ==
(TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) {
tpm_put8(tpm, TPM_ACCESS, TPM_ACCESS_ACTIVE_LOCALITY);
}
}
static int
tis_check_active_locality(tpm_state_t *tpm, char locality)
{
uint8_t access_bits;
uint8_t old_locality;
ASSERT(tpm != NULL && locality >= 0 && locality < 5);
old_locality = tpm->locality;
tpm->locality = locality;
access_bits = tpm_get8(tpm, TPM_ACCESS);
access_bits &= (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID);
tpm->locality = old_locality;
if (access_bits == (TPM_ACCESS_ACTIVE_LOCALITY | TPM_ACCESS_VALID)) {
return (DDI_SUCCESS);
} else {
return (DDI_FAILURE);
}
}
static int
tis_request_locality(tpm_state_t *tpm, char locality)
{
clock_t timeout;
int ret;
ASSERT(tpm != NULL && locality >= 0 && locality < 5);
ret = tis_check_active_locality(tpm, locality);
if (ret == DDI_SUCCESS) {
tpm->locality = locality;
return (DDI_SUCCESS);
}
tpm_put8(tpm, TPM_ACCESS, TPM_ACCESS_REQUEST_USE);
timeout = ddi_get_lbolt() + tpm->timeout_a;
while (tis_check_active_locality(tpm, locality) != DDI_SUCCESS) {
if (ddi_get_lbolt() >= timeout) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: (interrupt-disabled) "
"tis_request_locality timed out (timeout_a = %ld)",
__func__, tpm->timeout_a);
#endif
return (DDI_FAILURE);
}
delay(tpm->timeout_poll);
}
tpm->locality = locality;
return (DDI_SUCCESS);
}
static uint8_t
tis_get_status(tpm_state_t *tpm)
{
return (tpm_get8(tpm, TPM_STS));
}
static int
tpm_wait_for_stat(tpm_state_t *tpm, uint8_t mask, clock_t timeout)
{
clock_t absolute_timeout = ddi_get_lbolt() + timeout;
while ((tis_get_status(tpm) & mask) != mask) {
if (ddi_get_lbolt() >= absolute_timeout) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: using "
"polling - reached timeout (%ld usecs)",
__func__, drv_hztousec(timeout));
#endif
return (DDI_FAILURE);
}
delay(tpm->timeout_poll);
}
return (DDI_SUCCESS);
}
static int
tis_init(tpm_state_t *tpm)
{
uint32_t intf_caps;
int ret;
tpm->timeout_a = drv_usectohz(TIS_TIMEOUT_A);
tpm->timeout_b = drv_usectohz(TIS_TIMEOUT_B);
tpm->timeout_c = drv_usectohz(TIS_TIMEOUT_C);
tpm->timeout_d = drv_usectohz(TIS_TIMEOUT_D);
tpm->duration[TPM_SHORT] = drv_usectohz(TPM_DEFAULT_DURATION);
tpm->duration[TPM_MEDIUM] = drv_usectohz(TPM_DEFAULT_DURATION);
tpm->duration[TPM_LONG] = drv_usectohz(TPM_DEFAULT_DURATION);
tpm->duration[TPM_UNDEFINED] = drv_usectohz(TPM_DEFAULT_DURATION);
intf_caps = tpm_get32(tpm, TPM_INTF_CAP);
if (intf_caps & 0x7FFFFF00) {
cmn_err(CE_WARN, "!%s: bad intf_caps value 0x%0X",
__func__, intf_caps);
return (DDI_FAILURE);
}
if (!(intf_caps & TPM_INTF_INT_LOCALITY_CHANGE_INT)) {
cmn_err(CE_WARN,
"!%s: Mandatory capability Locality Change Int "
"not supported", __func__);
return (DDI_FAILURE);
}
if (!(intf_caps & TPM_INTF_INT_DATA_AVAIL_INT)) {
cmn_err(CE_WARN, "!%s: Mandatory capability Data Available Int "
"not supported.", __func__);
return (DDI_FAILURE);
}
ret = tis_request_locality(tpm, DEFAULT_LOCALITY);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "!%s: Unable to request locality %d", __func__,
DEFAULT_LOCALITY);
return (DDI_FAILURE);
}
tpm->timeout_poll = drv_usectohz(TPM_POLLING_TIMEOUT);
tpm->intr_enabled = 0;
ret = tpm_get_timeouts(tpm);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "!%s: tpm_get_timeouts error", __func__);
return (DDI_FAILURE);
}
ret = tpm_get_duration(tpm);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "!%s: tpm_get_duration error", __func__);
return (DDI_FAILURE);
}
ret = tpm_get_version(tpm);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "!%s: tpm_get_version error", __func__);
return (DDI_FAILURE);
}
ret = tpm_continue_selftest(tpm);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "!%s: tpm_continue_selftest error", __func__);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
_init(void)
{
int ret;
ret = ddi_soft_state_init(&statep, sizeof (tpm_state_t), 1);
if (ret) {
#ifdef DEBUG
cmn_err(CE_WARN, "!ddi_soft_state_init failed: %d", ret);
#endif
return (ret);
}
ret = mod_install(&tpm_ml);
if (ret != 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "!_init: mod_install returned non-zero");
#endif
ddi_soft_state_fini(&statep);
return (ret);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
int ret;
ret = mod_info(&tpm_ml, modinfop);
#ifdef DEBUG
if (ret == 0)
cmn_err(CE_WARN, "!mod_info failed: %d", ret);
#endif
return (ret);
}
int
_fini()
{
int ret;
ret = mod_remove(&tpm_ml);
if (ret != 0)
return (ret);
ddi_soft_state_fini(&statep);
return (ret);
}
static int
tpm_resume(tpm_state_t *tpm)
{
mutex_enter(&tpm->pm_mutex);
if (!tpm->suspended) {
mutex_exit(&tpm->pm_mutex);
return (DDI_FAILURE);
}
tpm->suspended = 0;
cv_broadcast(&tpm->suspend_cv);
mutex_exit(&tpm->pm_mutex);
return (DDI_SUCCESS);
}
#ifdef sun4v
static uint64_t hsvc_tpm_minor = 0;
static hsvc_info_t hsvc_tpm = {
HSVC_REV_1, NULL, HSVC_GROUP_TPM, 1, 0, NULL
};
#endif
static int
tpm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int ret;
int instance;
#ifndef sun4v
int idx, nregs;
#endif
tpm_state_t *tpm = NULL;
ASSERT(dip != NULL);
instance = ddi_get_instance(dip);
if (instance < 0)
return (DDI_FAILURE);
switch (cmd) {
case DDI_ATTACH:
if (ddi_soft_state_zalloc(statep, instance) == DDI_SUCCESS) {
tpm = ddi_get_soft_state(statep, instance);
if (tpm == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN,
"!%s: cannot get state information.",
__func__);
#endif
return (DDI_FAILURE);
}
tpm->dip = dip;
} else {
#ifdef DEBUG
cmn_err(CE_WARN,
"!%s: cannot allocate state information.",
__func__);
#endif
return (DDI_FAILURE);
}
break;
case DDI_RESUME:
tpm = ddi_get_soft_state(statep, instance);
if (tpm == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: cannot get state information.",
__func__);
#endif
return (DDI_FAILURE);
}
return (tpm_resume(tpm));
default:
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: cmd %d is not implemented", __func__,
cmd);
#endif
ret = DDI_FAILURE;
goto FAIL;
}
tpm->flags = 0;
#ifdef sun4v
ret = hsvc_register(&hsvc_tpm, &hsvc_tpm_minor);
if (ret != 0) {
cmn_err(CE_WARN, "!%s: failed to register with "
"hypervisor: 0x%0x", __func__, ret);
goto FAIL;
}
tpm->flags |= TPM_HSVC_REGISTERED;
#else
tpm->accattr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
tpm->accattr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
tpm->accattr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
idx = 0;
ret = ddi_dev_nregs(tpm->dip, &nregs);
if (ret != DDI_SUCCESS)
goto FAIL;
for (idx = 0; idx < nregs; idx++) {
off_t regsize;
if ((ret = ddi_dev_regsize(tpm->dip, idx, ®size)) !=
DDI_SUCCESS)
goto FAIL;
if (regsize == 0x5000)
break;
}
if (idx == nregs) {
ret = DDI_FAILURE;
goto FAIL;
}
ret = ddi_regs_map_setup(tpm->dip, idx, (caddr_t *)&tpm->addr,
(offset_t)0, (offset_t)0x5000,
&tpm->accattr, &tpm->handle);
if (ret != DDI_SUCCESS) {
goto FAIL;
}
tpm->flags |= TPM_DIDREGSMAP;
#endif
ret = tis_init(tpm);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_init() failed with error %d",
__func__, ret);
#endif
if (tpm->flags & TPM_DIDREGSMAP) {
ddi_regs_map_free(&tpm->handle);
tpm->handle = NULL;
tpm->flags &= ~TPM_DIDREGSMAP;
}
goto FAIL;
}
mutex_init(&tpm->dev_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&tpm->pm_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&tpm->suspend_cv, NULL, CV_DRIVER, NULL);
(void) ddi_prop_update_string(DDI_DEV_T_NONE, dip,
"pm-hardware-state", "needs-suspend-resume");
mutex_enter(&tpm->pm_mutex);
tpm->suspended = 0;
mutex_exit(&tpm->pm_mutex);
tpm->flags |= TPM_DID_MUTEX;
tpm->bufsize = TPM_IO_BUF_SIZE;
tpm->iobuf = kmem_zalloc((sizeof (uint8_t))*(tpm->bufsize), KM_SLEEP);
tpm->flags |= TPM_DID_IO_ALLOC;
mutex_init(&tpm->iobuf_lock, NULL, MUTEX_DRIVER, NULL);
tpm->flags |= TPM_DID_IO_MUTEX;
cv_init(&tpm->iobuf_cv, NULL, CV_DRIVER, NULL);
tpm->flags |= TPM_DID_IO_CV;
ret = ddi_create_minor_node(dip, "tpm", S_IFCHR, ddi_get_instance(dip),
DDI_PSEUDO, 0);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: ddi_create_minor_node failed", __func__);
#endif
goto FAIL;
}
tpm->flags |= TPM_DIDMINOR;
#ifdef KCF_TPM_RNG_PROVIDER
if (tpmrng_register(tpm) != DDI_SUCCESS)
cmn_err(CE_WARN, "!%s: tpm RNG failed to register with kcf",
__func__);
#endif
return (DDI_SUCCESS);
FAIL:
if (tpm != NULL) {
tpm_cleanup(dip, tpm);
ddi_soft_state_free(statep, instance);
tpm = NULL;
}
return (DDI_FAILURE);
}
static void
tpm_cleanup(dev_info_t *dip, tpm_state_t *tpm)
{
if (tpm == NULL)
return;
#ifdef KCF_TPM_RNG_PROVIDER
(void) tpmrng_unregister(tpm);
#endif
#ifdef sun4v
if (tpm->flags & TPM_HSVC_REGISTERED) {
(void) hsvc_unregister(&hsvc_tpm);
tpm->flags &= ~(TPM_HSVC_REGISTERED);
}
#endif
if (tpm->flags & TPM_DID_MUTEX) {
mutex_destroy(&tpm->dev_lock);
mutex_destroy(&tpm->pm_mutex);
cv_destroy(&tpm->suspend_cv);
tpm->flags &= ~(TPM_DID_MUTEX);
}
if (tpm->flags & TPM_DID_IO_ALLOC) {
ASSERT(tpm->iobuf != NULL);
kmem_free(tpm->iobuf, (sizeof (uint8_t))*(tpm->bufsize));
tpm->flags &= ~(TPM_DID_IO_ALLOC);
}
if (tpm->flags & TPM_DID_IO_MUTEX) {
mutex_destroy(&tpm->iobuf_lock);
tpm->flags &= ~(TPM_DID_IO_MUTEX);
}
if (tpm->flags & TPM_DID_IO_CV) {
cv_destroy(&tpm->iobuf_cv);
tpm->flags &= ~(TPM_DID_IO_CV);
}
if (tpm->flags & TPM_DIDREGSMAP) {
if (tpm->handle != NULL)
ddi_regs_map_free(&tpm->handle);
tpm->flags &= ~(TPM_DIDREGSMAP);
}
if (tpm->flags & TPM_DIDMINOR) {
ddi_remove_minor_node(dip, NULL);
tpm->flags &= ~(TPM_DIDMINOR);
}
}
static int
tpm_suspend(tpm_state_t *tpm)
{
if (tpm == NULL)
return (DDI_FAILURE);
mutex_enter(&tpm->pm_mutex);
if (tpm->suspended) {
mutex_exit(&tpm->pm_mutex);
return (DDI_SUCCESS);
}
tpm->suspended = 1;
mutex_exit(&tpm->pm_mutex);
return (DDI_SUCCESS);
}
static int
tpm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
tpm_state_t *tpm;
ASSERT(dip != NULL);
instance = ddi_get_instance(dip);
if (instance < 0)
return (DDI_FAILURE);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (ENXIO);
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (tpm_suspend(tpm));
default:
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: case %d not implemented", __func__, cmd);
#endif
return (DDI_FAILURE);
}
tpm_cleanup(dip, tpm);
ddi_soft_state_free(statep, instance);
tpm = NULL;
return (DDI_SUCCESS);
}
static int
tpm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
int instance;
tpm_state_t *tpm;
instance = ddi_get_instance(dip);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
*resultp = tpm->dip;
break;
case DDI_INFO_DEVT2INSTANCE:
*resultp = 0;
break;
default:
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: cmd %d is not implemented", __func__,
cmd);
#endif
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
tpm_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
int instance;
tpm_state_t *tpm;
ASSERT(devp != NULL);
instance = getminor(*devp);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (ENXIO);
}
if (otyp != OTYP_CHR) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: otyp(%d) != OTYP_CHR(%d)",
__func__, otyp, OTYP_CHR);
#endif
return (EINVAL);
}
TPM_EXCLUSIVE_LOCK(tpm);
mutex_enter(&tpm->dev_lock);
if (tpm->dev_held) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: the device is already being used",
__func__);
#endif
mutex_exit(&tpm->dev_lock);
return (EBUSY);
}
tpm->dev_held = 1;
mutex_exit(&tpm->dev_lock);
return (0);
}
static int
tpm_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
int instance;
tpm_state_t *tpm;
instance = getminor(dev);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (ENXIO);
}
if (otyp != OTYP_CHR) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: otyp(%d) != OTYP_CHR(%d)",
__func__, otyp, OTYP_CHR);
#endif
return (EINVAL);
}
TPM_EXCLUSIVE_LOCK(tpm);
ASSERT(tpm->dev_held);
mutex_enter(&tpm->dev_lock);
ASSERT(mutex_owned(&tpm->dev_lock));
tpm->dev_held = 0;
mutex_exit(&tpm->dev_lock);
return (0);
}
static int
tpm_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
int ret;
uint32_t size;
int instance;
tpm_state_t *tpm;
instance = getminor(dev);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (ENXIO);
}
if (uiop == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: passed in uiop is NULL", __func__);
#endif
return (EFAULT);
}
TPM_EXCLUSIVE_LOCK(tpm);
ret = tpm_io_lock(tpm);
if (ret)
return (ret);
if (uiop->uio_resid > tpm->bufsize) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: read_in data is bigger "
"than tpm->bufsize:read in:%d, bufsiz:%d",
__func__, (int)uiop->uio_resid, (int)tpm->bufsize);
#endif
ret = EIO;
goto OUT;
}
ret = tis_recv_data(tpm, tpm->iobuf, tpm->bufsize);
if (ret < TPM_HEADER_SIZE) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_recv_data returned error", __func__);
#endif
ret = EIO;
goto OUT;
}
size = load32(tpm->iobuf, 2);
if (ret != size) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_recv_data:"
"expected size=%d, actually read=%d",
__func__, size, ret);
#endif
ret = EIO;
goto OUT;
}
ret = uiomove(tpm->iobuf, size, UIO_READ, uiop);
if (ret) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: uiomove returned error", __func__);
#endif
goto OUT;
}
bzero(tpm->iobuf, tpm->bufsize);
ret = DDI_SUCCESS;
OUT:
tpm_unlock(tpm);
return (ret);
}
static int
tpm_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
int ret;
size_t len;
uint32_t size;
int instance;
tpm_state_t *tpm;
instance = getminor(dev);
if ((tpm = ddi_get_soft_state(statep, instance)) == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: stored pointer to tpm state is NULL",
__func__);
#endif
return (ENXIO);
}
if (uiop == NULL) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: passed in uiop is NULL", __func__);
#endif
return (EFAULT);
}
TPM_EXCLUSIVE_LOCK(tpm);
len = uiop->uio_resid;
if (len == 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: requested read of len 0", __func__);
#endif
return (0);
}
ret = tpm_io_lock(tpm);
if (ret)
return (ret);
ret = uiomove(tpm->iobuf, TPM_HEADER_SIZE, UIO_WRITE, uiop);
if (ret) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: uiomove returned error"
"while getting the the header",
__func__);
#endif
goto OUT;
}
size = load32(tpm->iobuf, TPM_PARAMSIZE_OFFSET);
if (size > tpm->bufsize) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: size %d is greater than "
"the tpm input buffer size %d",
__func__, (int)size, (int)tpm->bufsize);
#endif
ret = ENXIO;
goto OUT;
}
ret = uiomove(tpm->iobuf+TPM_HEADER_SIZE, size-TPM_HEADER_SIZE,
UIO_WRITE, uiop);
if (ret) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: uiomove returned error"
"while getting the rest of the command", __func__);
#endif
goto OUT;
}
ret = tis_send_data(tpm, tpm->iobuf, size);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!%s: tis_send_data returned error", __func__);
#endif
ret = EFAULT;
goto OUT;
}
bzero(tpm->iobuf, tpm->bufsize);
ret = DDI_SUCCESS;
OUT:
tpm_unlock(tpm);
return (ret);
}
static inline int
tpm_io_lock(tpm_state_t *tpm)
{
int ret;
clock_t timeout;
mutex_enter(&tpm->iobuf_lock);
ASSERT(mutex_owned(&tpm->iobuf_lock));
timeout = ddi_get_lbolt() + drv_usectohz(TPM_IO_TIMEOUT);
while (tpm->iobuf_inuse) {
ret = cv_timedwait(&tpm->iobuf_cv, &tpm->iobuf_lock, timeout);
if (ret <= 0) {
mutex_exit(&tpm->iobuf_lock);
#ifdef DEBUG
cmn_err(CE_WARN, "!tpm_io_lock:iorequest timed out");
#endif
return (ETIME);
}
}
tpm->iobuf_inuse = 1;
mutex_exit(&tpm->iobuf_lock);
return (0);
}
static inline void
tpm_unlock(tpm_state_t *tpm)
{
mutex_enter(&tpm->iobuf_lock);
ASSERT(tpm->iobuf_inuse == 1 && mutex_owned(&tpm->iobuf_lock));
tpm->iobuf_inuse = 0;
cv_broadcast(&tpm->iobuf_cv);
mutex_exit(&tpm->iobuf_lock);
}
#ifdef KCF_TPM_RNG_PROVIDER
static void
strncpy_spacepad(uchar_t *s1, char *s2, int n)
{
int s2len = strlen(s2);
(void) strncpy((char *)s1, s2, n);
if (s2len < n)
(void) memset(s1 + s2len, ' ', n - s2len);
}
static int
tpmrng_ext_info(crypto_provider_handle_t prov,
crypto_provider_ext_info_t *ext_info,
crypto_req_handle_t cfreq)
{
tpm_state_t *tpm = (tpm_state_t *)prov;
char buf[64];
if (tpm == NULL)
return (DDI_FAILURE);
strncpy_spacepad(ext_info->ei_manufacturerID,
(char *)tpm->vers_info.tpmVendorID,
sizeof (ext_info->ei_manufacturerID));
strncpy_spacepad(ext_info->ei_model, "0",
sizeof (ext_info->ei_model));
strncpy_spacepad(ext_info->ei_serial_number, "0",
sizeof (ext_info->ei_serial_number));
ext_info->ei_flags = CRYPTO_EXTF_RNG | CRYPTO_EXTF_SO_PIN_LOCKED;
ext_info->ei_max_session_count = CRYPTO_EFFECTIVELY_INFINITE;
ext_info->ei_max_pin_len = 0;
ext_info->ei_min_pin_len = 0;
ext_info->ei_total_public_memory = CRYPTO_UNAVAILABLE_INFO;
ext_info->ei_free_public_memory = CRYPTO_UNAVAILABLE_INFO;
ext_info->ei_total_private_memory = CRYPTO_UNAVAILABLE_INFO;
ext_info->ei_free_public_memory = CRYPTO_UNAVAILABLE_INFO;
ext_info->ei_time[0] = 0;
ext_info->ei_hardware_version.cv_major = tpm->vers_info.version.major;
ext_info->ei_hardware_version.cv_minor = tpm->vers_info.version.minor;
ext_info->ei_firmware_version.cv_major =
tpm->vers_info.version.revMajor;
ext_info->ei_firmware_version.cv_minor =
tpm->vers_info.version.revMinor;
(void) snprintf(buf, sizeof (buf), "tpmrng TPM RNG");
strncpy_spacepad(ext_info->ei_label, buf,
sizeof (ext_info->ei_label));
#undef BUFSZ
return (CRYPTO_SUCCESS);
}
static int
tpmrng_register(tpm_state_t *tpm)
{
int ret;
char ID[64];
crypto_mech_name_t *rngmech;
ASSERT(tpm != NULL);
(void) snprintf(ID, sizeof (ID), "tpmrng %s", IDENT_TPMRNG);
tpmrng_prov_info.pi_provider_description = ID;
tpmrng_prov_info.pi_provider_dev.pd_hw = tpm->dip;
tpmrng_prov_info.pi_provider_handle = tpm;
ret = crypto_register_provider(&tpmrng_prov_info, &tpm->n_prov);
if (ret != CRYPTO_SUCCESS) {
tpm->n_prov = NULL;
return (DDI_FAILURE);
}
crypto_provider_notification(tpm->n_prov, CRYPTO_PROVIDER_READY);
rngmech = kmem_zalloc(strlen("random") + 1, KM_SLEEP);
(void) memcpy(rngmech, "random", 6);
ret = crypto_load_dev_disabled("tpm", ddi_get_instance(tpm->dip),
1, rngmech);
#ifdef DEBUG
if (ret != CRYPTO_SUCCESS)
cmn_err(CE_WARN, "!crypto_load_dev_disabled failed (%d)", ret);
#endif
return (DDI_SUCCESS);
}
static int
tpmrng_unregister(tpm_state_t *tpm)
{
int ret;
ASSERT(tpm != NULL);
if (tpm->n_prov) {
ret = crypto_unregister_provider(tpm->n_prov);
tpm->n_prov = NULL;
if (ret != CRYPTO_SUCCESS)
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static void
tpmrng_provider_status(crypto_provider_handle_t provider, uint_t *status)
{
*status = CRYPTO_PROVIDER_READY;
}
static int
tpmrng_seed_random(crypto_provider_handle_t provider, crypto_session_id_t sid,
uchar_t *buf, size_t len, uint_t entropy_est, uint32_t flags,
crypto_req_handle_t req)
{
int ret;
tpm_state_t *tpm;
uint32_t len32;
uint8_t cmdbuf[270] = {
0, 193,
0, 0, 0, 0x0A,
0, 0, 0, TPM_ORD_StirRandom,
0, 0, 0, 0
};
uint32_t buflen;
if (len == 0 || len > 255 || buf == NULL)
return (CRYPTO_ARGUMENTS_BAD);
tpm = (tpm_state_t *)provider;
if (tpm == NULL)
return (CRYPTO_INVALID_CONTEXT);
TPM_EXCLUSIVE_LOCK(tpm);
ret = tpm_io_lock(tpm);
if (ret)
return (CRYPTO_BUSY);
len32 = (uint32_t)len;
buflen = len32 + 14;
buflen = htonl(buflen);
bcopy(&buflen, cmdbuf + 2, sizeof (uint32_t));
buflen = ntohl(buflen);
len32 = htonl(len32);
bcopy(&len32, cmdbuf + 10, sizeof (uint32_t));
len32 = ntohl(len32);
bcopy(buf, cmdbuf + 14, len32);
ret = itpm_command(tpm, cmdbuf, buflen);
tpm_unlock(tpm);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!tpmrng_seed_random failed");
#endif
return (CRYPTO_FAILED);
}
return (CRYPTO_SUCCESS);
}
static int
tpmrng_generate_random(crypto_provider_handle_t provider,
crypto_session_id_t sid, uchar_t *buf, size_t len, crypto_req_handle_t req)
{
int ret;
tpm_state_t *tpm;
uint8_t hdr[14] = {
0, 193,
0, 0, 0, 14,
0, 0, 0, TPM_ORD_GetRandom,
0, 0, 0, 0
};
uint8_t *cmdbuf = NULL;
uint32_t len32 = (uint32_t)len;
uint32_t buflen = len32 + sizeof (hdr);
if (len == 0 || buf == NULL)
return (CRYPTO_ARGUMENTS_BAD);
tpm = (tpm_state_t *)provider;
if (tpm == NULL)
return (CRYPTO_INVALID_CONTEXT);
TPM_EXCLUSIVE_LOCK(tpm);
ret = tpm_io_lock(tpm);
if (ret)
return (CRYPTO_BUSY);
cmdbuf = kmem_zalloc(buflen, KM_SLEEP);
bcopy(hdr, cmdbuf, sizeof (hdr));
len32 = htonl(len32);
bcopy(&len32, cmdbuf + 10, sizeof (uint32_t));
ret = itpm_command(tpm, cmdbuf, buflen);
if (ret != DDI_SUCCESS) {
#ifdef DEBUG
cmn_err(CE_WARN, "!tpmrng_generate_random failed");
#endif
kmem_free(cmdbuf, buflen);
tpm_unlock(tpm);
return (CRYPTO_FAILED);
}
len32 = load32(cmdbuf, 10);
bcopy(cmdbuf + 14, buf, len32);
kmem_free(cmdbuf, buflen);
tpm_unlock(tpm);
return (CRYPTO_SUCCESS);
}
#endif