blob: 8e2f80c34c24433fa4870d324828dbc717af711a [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Very basic TPM driver
*
* Assumptions:
* - This driver is the sole owner of the TPM hardware. While the TPM hardware
* supports co-ownership, this code does not handle being kicked off the TPM.
* - The system firmware is responsible for initializing the TPM and has
* already done so.
*/
#include <assert.h>
#include <endian.h>
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/io-buffer.h>
#include <zircon/device/tpm.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include "tpm.h"
#include "tpm-commands.h"
#define TPM_PHYS_ADDRESS 0xfed40000
#define TPM_PHYS_LENGTH 0x5000
// This is arbitrary, we just want to limit the size of the response buffer
// that we need to allocate.
#define MAX_RAND_BYTES 256
static mtx_t tpm_lock = MTX_INIT;
void *tpm_base;
zx_handle_t irq_handle;
static io_buffer_t io_buffer;
// implement tpm protocol:
static ssize_t tpm_get_random(zx_device_t* dev, void* buf, size_t count) {
if (count > MAX_RAND_BYTES) {
count = MAX_RAND_BYTES;
}
struct tpm_getrandom_cmd cmd;
uint32_t resp_len = tpm_init_getrandom(&cmd, count);
struct tpm_getrandom_resp *resp = malloc(resp_len);
if (!resp) {
return ZX_ERR_NO_MEMORY;
}
mtx_lock(&tpm_lock);
zx_status_t status = tpm_send_cmd(LOCALITY0, (uint8_t*)&cmd, sizeof(cmd));
if (status != ZX_OK) {
goto cleanup;
}
status = tpm_recv_resp(LOCALITY0, (uint8_t*)resp, resp_len);
if (status < 0) {
goto cleanup;
}
if ((uint32_t)status < sizeof(*resp) ||
(uint32_t)status != betoh32(resp->hdr.total_len)) {
status = ZX_ERR_BAD_STATE;
goto cleanup;
}
uint32_t bytes_returned = betoh32(resp->bytes_returned);
if ((uint32_t)status != sizeof(*resp) + bytes_returned ||
resp->hdr.tag != htobe16(TPM_TAG_RSP_COMMAND) ||
bytes_returned > count ||
resp->hdr.return_code != htobe32(TPM_SUCCESS)) {
status = ZX_ERR_BAD_STATE;
goto cleanup;
}
memcpy(buf, resp->bytes, bytes_returned);
memset(resp->bytes, 0, bytes_returned);
status = bytes_returned;
cleanup:
free(resp);
mtx_unlock(&tpm_lock);
return status;
}
static zx_status_t tpm_save_state(void) {
struct tpm_savestate_cmd cmd;
uint32_t resp_len = tpm_init_savestate(&cmd);
struct tpm_savestate_resp resp;
mtx_lock(&tpm_lock);
zx_status_t status = tpm_send_cmd(LOCALITY0, (uint8_t*)&cmd, sizeof(cmd));
if (status != ZX_OK) {
goto cleanup;
}
status = tpm_recv_resp(LOCALITY0, (uint8_t*)&resp, resp_len);
if (status < 0) {
goto cleanup;
}
if ((uint32_t)status < sizeof(resp) ||
(uint32_t)status != betoh32(resp.hdr.total_len) ||
resp.hdr.tag != htobe16(TPM_TAG_RSP_COMMAND) ||
resp.hdr.return_code != htobe32(TPM_SUCCESS)) {
status = ZX_ERR_BAD_STATE;
goto cleanup;
}
status = ZX_OK;
cleanup:
mtx_unlock(&tpm_lock);
return status;
}
static zx_status_t tpm_device_ioctl(void* ctx, uint32_t op,
const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
switch (op) {
case IOCTL_TPM_SAVE_STATE: return tpm_save_state();
}
return ZX_ERR_NOT_SUPPORTED;
}
// implement device protocol:
static zx_protocol_device_t tpm_device_proto __UNUSED = {
.version = DEVICE_OPS_VERSION,
.ioctl = tpm_device_ioctl,
};
//TODO: bind against hw, not misc
zx_status_t tpm_bind(void* ctx, zx_device_t* parent, void** cookie) {
#if defined(__x86_64__) || defined(__i386__)
zx_status_t status = io_buffer_init_physical(&io_buffer, TPM_PHYS_ADDRESS, TPM_PHYS_LENGTH,
get_root_resource(), ZX_CACHE_POLICY_UNCACHED);
if (status != ZX_OK) {
return status;
}
tpm_base = io_buffer_virt(&io_buffer);
status = tpm_is_supported(LOCALITY0);
if (status != ZX_OK) {
return status;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "tpm",
.ops = &tpm_device_proto,
.proto_id = ZX_PROTOCOL_TPM,
};
zx_device_t* dev;
status = device_add(parent, &args, &dev);
if (status != ZX_OK) {
return status;
}
// tpm_request_use will fail if we're not at least 30ms past _TPM_INIT.
// The system firmware performs the init, so it's safe to assume that
// is 30 ms past. If we're on systems where we need to do init,
// we need to wait up to 30ms for the TPM_ACCESS register to be valid.
status = tpm_request_use(LOCALITY0);
if (status != ZX_OK) {
goto cleanup_device;
}
status = tpm_wait_for_locality(LOCALITY0);
if (status != ZX_OK) {
goto cleanup_device;
}
// Configure interrupts
status = tpm_set_irq(LOCALITY0, 10);
if (status != ZX_OK) {
goto cleanup_device;
}
status = zx_interrupt_create(get_root_resource(), 10, ZX_FLAG_REMAP_IRQ, &irq_handle);
if (status != ZX_OK) {
goto cleanup_device;
}
status = tpm_enable_irq_type(LOCALITY0, IRQ_DATA_AVAIL);
if (status < 0) {
goto cleanup_device;
}
status = tpm_enable_irq_type(LOCALITY0, IRQ_LOCALITY_CHANGE);
if (status < 0) {
goto cleanup_device;
}
// Make a best-effort attempt to give the kernel some more entropy
// TODO(security): Perform a more recurring seeding
uint8_t buf[32] = { 0 };
ssize_t bytes_read = tpm_get_random(dev, buf, sizeof(buf));
if (bytes_read > 0) {
zx_cprng_add_entropy(buf, bytes_read);
memset(buf, 0, sizeof(buf));
}
return ZX_OK;
cleanup_device:
if (irq_handle > 0) {
zx_handle_close(irq_handle);
}
device_remove(dev);
return status;
#else
return ZX_ERR_NOT_SUPPORTED;
#endif
}
static zx_driver_ops_t tpm_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = tpm_bind,
};
ZIRCON_DRIVER_BEGIN(tpm, tpm_driver_ops, "zircon", "0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
ZIRCON_DRIVER_END(tpm)