| // 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: |
| * - The system firmware is responsible for initializing the TPM and has |
| * already done so. |
| */ |
| |
| #include <assert.h> |
| #include <endian.h> |
| #include <ddk/debug.h> |
| #include <ddk/driver.h> |
| #include <ddk/io-buffer.h> |
| #include <ddk/protocol/i2c.h> |
| #include <explicit-memory/bytes.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/unique_ptr.h> |
| #include <fbl/unique_free_ptr.h> |
| #include <zircon/types.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <threads.h> |
| |
| #include <utility> |
| |
| #include "i2c-cr50.h" |
| #include "tpm.h" |
| #include "tpm-commands.h" |
| |
| // This is arbitrary, we just want to limit the size of the response buffer |
| // that we need to allocate. |
| #define MAX_RAND_BYTES 256 |
| |
| namespace tpm { |
| |
| // implement tpm protocol: |
| |
| static zx_status_t GetRandom(Device* dev, void* buf, uint16_t count, size_t* actual) { |
| static_assert(MAX_RAND_BYTES <= UINT32_MAX, ""); |
| if (count > MAX_RAND_BYTES) { |
| count = MAX_RAND_BYTES; |
| } |
| |
| struct tpm_getrandom_cmd cmd; |
| uint32_t resp_len = tpm_init_getrandom(&cmd, count); |
| fbl::unique_free_ptr<tpm_getrandom_resp> resp( |
| reinterpret_cast<tpm_getrandom_resp*>(malloc(resp_len))); |
| size_t actual_read; |
| uint16_t bytes_returned; |
| if (!resp) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = dev->ExecuteCmd(0, (uint8_t*)&cmd, sizeof(cmd), |
| (uint8_t*)resp.get(), resp_len, &actual_read); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (actual_read < sizeof(*resp) || |
| actual_read != betoh32(resp->hdr.total_len)) { |
| |
| return ZX_ERR_BAD_STATE; |
| } |
| bytes_returned = betoh16(resp->bytes_returned); |
| if (actual_read != sizeof(*resp) + bytes_returned || |
| resp->hdr.tag != htobe16(TPM_ST_NO_SESSIONS) || |
| bytes_returned > count || |
| resp->hdr.return_code != htobe32(TPM_SUCCESS)) { |
| |
| return ZX_ERR_BAD_STATE; |
| } |
| memcpy(buf, resp->bytes, bytes_returned); |
| mandatory_memset(resp->bytes, 0, bytes_returned); |
| *actual = bytes_returned; |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::ShutdownLocked(uint16_t type) { |
| struct tpm_shutdown_cmd cmd; |
| uint32_t resp_len = tpm_init_shutdown(&cmd, type); |
| struct tpm_shutdown_resp resp; |
| size_t actual; |
| |
| zx_status_t status = ExecuteCmdLocked(0, (uint8_t*)&cmd, sizeof(cmd), |
| (uint8_t*)&resp, resp_len, &actual); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (actual < sizeof(resp) || |
| actual != betoh32(resp.hdr.total_len) || |
| resp.hdr.tag != htobe16(TPM_ST_NO_SESSIONS) || |
| resp.hdr.return_code != htobe32(TPM_SUCCESS)) { |
| |
| return ZX_ERR_BAD_STATE; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::ExecuteCmd(Locality loc, const uint8_t* cmd, size_t len, |
| uint8_t* resp, size_t max_len, size_t* actual) { |
| fbl::AutoLock guard(&lock_); |
| return ExecuteCmdLocked(loc, cmd, len, resp, max_len, actual); |
| } |
| |
| zx_status_t Device::ExecuteCmdLocked(Locality loc, const uint8_t* cmd, size_t len, |
| uint8_t* resp, size_t max_len, size_t* actual) { |
| zx_status_t status = SendCmdLocked(loc, cmd, len); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return RecvRespLocked(loc, resp, max_len, actual); |
| } |
| |
| void Device::DdkRelease() { |
| delete this; |
| } |
| |
| zx_status_t Device::DdkSuspend(uint32_t flags) { |
| fbl::AutoLock guard(&lock_); |
| |
| if (flags == DEVICE_SUSPEND_FLAG_SUSPEND_RAM) { |
| zx_status_t status = ShutdownLocked(TPM_SU_STATE); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "tpm: Failed to save state: %d\n", status); |
| return status; |
| } |
| } |
| |
| zx_status_t status = ReleaseLocalityLocked(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "tpm: Failed to release locality: %d\n", status); |
| return status; |
| } |
| return status; |
| } |
| |
| zx_status_t Device::Bind() { |
| zx_status_t status = DdkAdd("tpm", DEVICE_ADD_INVISIBLE); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| thrd_t thread; |
| int ret = thrd_create_with_name(&thread, Init, this, "tpm:slow_bind"); |
| if (ret != thrd_success) { |
| DdkRemove(); |
| return ZX_ERR_INTERNAL; |
| } |
| thrd_detach(thread); |
| return ZX_OK; |
| } |
| |
| zx_status_t Device::Init() { |
| uint8_t buf[32] = { 0 }; |
| size_t bytes_read; |
| |
| auto cleanup = fbl::MakeAutoCall([&] { |
| DdkRemove(); |
| }); |
| |
| zx_status_t status = iface_->Validate(); |
| if (status != ZX_OK) { |
| zxlogf(TRACE, "tpm: did not pass driver validation\n"); |
| return status; |
| } |
| |
| { |
| fbl::AutoLock guard(&lock_); |
| |
| // 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 = RequestLocalityLocked(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "tpm: Failed to request use: %d\n", status); |
| return status; |
| } |
| |
| status = WaitForLocalityLocked(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "tpm: Waiting for locality failed: %d\n", status); |
| return status; |
| } |
| } |
| |
| DdkMakeVisible(); |
| |
| // Make a best-effort attempt to give the kernel some more entropy |
| // TODO(security): Perform a more recurring seeding |
| status = tpm::GetRandom(this, buf, static_cast<uint16_t>(sizeof(buf)), &bytes_read); |
| if (status == ZX_OK) { |
| zx_cprng_add_entropy(buf, bytes_read); |
| mandatory_memset(buf, 0, sizeof(buf)); |
| } else { |
| zxlogf(ERROR, "tpm: Failed to add entropy to kernel CPRNG\n"); |
| } |
| |
| cleanup.cancel(); |
| return ZX_OK; |
| } |
| |
| Device::Device(zx_device_t* parent, fbl::unique_ptr<HardwareInterface> iface) |
| : DeviceType(parent), iface_(std::move(iface)) { |
| ddk_proto_id_ = ZX_PROTOCOL_TPM; |
| } |
| |
| Device::~Device() { |
| } |
| |
| } // namespace tpm |
| |
| zx_status_t tpm_bind(void* ctx, zx_device_t* parent) { |
| zx::handle irq; |
| i2c_protocol_t i2c; |
| zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &i2c); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "tpm: could not get I2C protocol: %d\n", status); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| status = i2c_get_interrupt(&i2c, 0, irq.reset_and_get_address()); |
| if (status == ZX_OK) { |
| // irq contains garbage? |
| zx_handle_t ignored __UNUSED = irq.release(); |
| } |
| |
| fbl::unique_ptr<tpm::I2cCr50Interface> i2c_iface; |
| status = tpm::I2cCr50Interface::Create(parent, std::move(irq), &i2c_iface); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| fbl::unique_ptr<tpm::Device> device(new (&ac) tpm::Device(parent, std::move(i2c_iface))); |
| if (!ac.check()) { |
| return status; |
| } |
| |
| status = device->Bind(); |
| if (status == ZX_OK) { |
| // DevMgr now owns this pointer, release it to avoid destroying the |
| // object when device goes out of scope. |
| __UNUSED auto ptr = device.release(); |
| } |
| return status; |
| } |