1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/* 6 * Very basic TPM driver 7 * 8 * Assumptions: 9 * - The system firmware is responsible for initializing the TPM and has 10 * already done so. 11 */ 12 13#include <assert.h> 14#include <endian.h> 15#include <ddk/debug.h> 16#include <ddk/driver.h> 17#include <ddk/io-buffer.h> 18#include <ddk/protocol/i2c.h> 19#include <explicit-memory/bytes.h> 20#include <fbl/alloc_checker.h> 21#include <fbl/auto_call.h> 22#include <fbl/auto_lock.h> 23#include <fbl/unique_ptr.h> 24#include <fbl/unique_free_ptr.h> 25#include <zircon/device/i2c.h> 26#include <zircon/device/tpm.h> 27#include <zircon/types.h> 28#include <stdlib.h> 29#include <stdio.h> 30#include <string.h> 31#include <threads.h> 32 33#include "i2c-cr50.h" 34#include "tpm.h" 35#include "tpm-commands.h" 36 37// This is arbitrary, we just want to limit the size of the response buffer 38// that we need to allocate. 39#define MAX_RAND_BYTES 256 40 41namespace tpm { 42 43// implement tpm protocol: 44 45static zx_status_t GetRandom(Device* dev, void* buf, uint16_t count, size_t* actual) { 46 static_assert(MAX_RAND_BYTES <= UINT32_MAX, ""); 47 if (count > MAX_RAND_BYTES) { 48 count = MAX_RAND_BYTES; 49 } 50 51 struct tpm_getrandom_cmd cmd; 52 uint32_t resp_len = tpm_init_getrandom(&cmd, count); 53 fbl::unique_free_ptr<tpm_getrandom_resp> resp( 54 reinterpret_cast<tpm_getrandom_resp*>(malloc(resp_len))); 55 size_t actual_read; 56 uint16_t bytes_returned; 57 if (!resp) { 58 return ZX_ERR_NO_MEMORY; 59 } 60 61 zx_status_t status = dev->ExecuteCmd(0, (uint8_t*)&cmd, sizeof(cmd), 62 (uint8_t*)resp.get(), resp_len, &actual_read); 63 if (status != ZX_OK) { 64 return status; 65 } 66 if (actual_read < sizeof(*resp) || 67 actual_read != betoh32(resp->hdr.total_len)) { 68 69 return ZX_ERR_BAD_STATE; 70 } 71 bytes_returned = betoh16(resp->bytes_returned); 72 if (actual_read != sizeof(*resp) + bytes_returned || 73 resp->hdr.tag != htobe16(TPM_ST_NO_SESSIONS) || 74 bytes_returned > count || 75 resp->hdr.return_code != htobe32(TPM_SUCCESS)) { 76 77 return ZX_ERR_BAD_STATE; 78 } 79 memcpy(buf, resp->bytes, bytes_returned); 80 mandatory_memset(resp->bytes, 0, bytes_returned); 81 *actual = bytes_returned; 82 return ZX_OK; 83} 84 85zx_status_t Device::ShutdownLocked(uint16_t type) { 86 struct tpm_shutdown_cmd cmd; 87 uint32_t resp_len = tpm_init_shutdown(&cmd, type); 88 struct tpm_shutdown_resp resp; 89 size_t actual; 90 91 zx_status_t status = ExecuteCmdLocked(0, (uint8_t*)&cmd, sizeof(cmd), 92 (uint8_t*)&resp, resp_len, &actual); 93 if (status != ZX_OK) { 94 return status; 95 } 96 if (actual < sizeof(resp) || 97 actual != betoh32(resp.hdr.total_len) || 98 resp.hdr.tag != htobe16(TPM_ST_NO_SESSIONS) || 99 resp.hdr.return_code != htobe32(TPM_SUCCESS)) { 100 101 return ZX_ERR_BAD_STATE; 102 } 103 return ZX_OK; 104} 105 106zx_status_t Device::ExecuteCmd(Locality loc, const uint8_t* cmd, size_t len, 107 uint8_t* resp, size_t max_len, size_t* actual) { 108 fbl::AutoLock guard(&lock_); 109 return ExecuteCmdLocked(loc, cmd, len, resp, max_len, actual); 110} 111 112zx_status_t Device::ExecuteCmdLocked(Locality loc, const uint8_t* cmd, size_t len, 113 uint8_t* resp, size_t max_len, size_t* actual) { 114 zx_status_t status = SendCmdLocked(loc, cmd, len); 115 if (status != ZX_OK) { 116 return status; 117 } 118 return RecvRespLocked(loc, resp, max_len, actual); 119} 120 121void Device::DdkRelease() { 122 delete this; 123} 124 125zx_status_t Device::DdkIoctl(uint32_t op, 126 const void* in_buf, size_t in_len, 127 void* out_buf, size_t out_len, size_t* out_actual) { 128 switch (op) { 129 case IOCTL_TPM_SAVE_STATE: { 130 fbl::AutoLock guard(&lock_); 131 return ShutdownLocked(TPM_SU_STATE); 132 } 133 } 134 return ZX_ERR_NOT_SUPPORTED; 135} 136 137zx_status_t Device::DdkSuspend(uint32_t flags) { 138 fbl::AutoLock guard(&lock_); 139 140 if (flags == DEVICE_SUSPEND_FLAG_SUSPEND_RAM) { 141 zx_status_t status = ShutdownLocked(TPM_SU_STATE); 142 if (status != ZX_OK) { 143 zxlogf(ERROR, "tpm: Failed to save state: %d\n", status); 144 return status; 145 } 146 } 147 148 zx_status_t status = ReleaseLocalityLocked(0); 149 if (status != ZX_OK) { 150 zxlogf(ERROR, "tpm: Failed to release locality: %d\n", status); 151 return status; 152 } 153 return status; 154} 155 156zx_status_t Device::Bind() { 157 zx_status_t status = DdkAdd("tpm", DEVICE_ADD_INVISIBLE); 158 if (status != ZX_OK) { 159 return status; 160 } 161 162 thrd_t thread; 163 int ret = thrd_create_with_name(&thread, Init, this, "tpm:slow_bind"); 164 if (ret != thrd_success) { 165 DdkRemove(); 166 return ZX_ERR_INTERNAL; 167 } 168 thrd_detach(thread); 169 return ZX_OK; 170} 171 172zx_status_t Device::Init() { 173 uint8_t buf[32] = { 0 }; 174 size_t bytes_read; 175 176 auto cleanup = fbl::MakeAutoCall([&] { 177 DdkRemove(); 178 }); 179 180 zx_status_t status = iface_->Validate(); 181 if (status != ZX_OK) { 182 zxlogf(TRACE, "tpm: did not pass driver validation\n"); 183 return status; 184 } 185 186 { 187 fbl::AutoLock guard(&lock_); 188 189 // tpm_request_use will fail if we're not at least 30ms past _TPM_INIT. 190 // The system firmware performs the init, so it's safe to assume that 191 // is 30 ms past. If we're on systems where we need to do init, 192 // we need to wait up to 30ms for the TPM_ACCESS register to be valid. 193 status = RequestLocalityLocked(0); 194 if (status != ZX_OK) { 195 zxlogf(ERROR, "tpm: Failed to request use: %d\n", status); 196 return status; 197 } 198 199 status = WaitForLocalityLocked(0); 200 if (status != ZX_OK) { 201 zxlogf(ERROR, "tpm: Waiting for locality failed: %d\n", status); 202 return status; 203 } 204 } 205 206 DdkMakeVisible(); 207 208 // Make a best-effort attempt to give the kernel some more entropy 209 // TODO(security): Perform a more recurring seeding 210 status = tpm::GetRandom(this, buf, static_cast<uint16_t>(sizeof(buf)), &bytes_read); 211 if (status == ZX_OK) { 212 zx_cprng_add_entropy(buf, bytes_read); 213 mandatory_memset(buf, 0, sizeof(buf)); 214 } else { 215 zxlogf(ERROR, "tpm: Failed to add entropy to kernel CPRNG\n"); 216 } 217 218 cleanup.cancel(); 219 return ZX_OK; 220} 221 222Device::Device(zx_device_t* parent, fbl::unique_ptr<HardwareInterface> iface) 223 : DeviceType(parent), iface_(fbl::move(iface)) { 224 ddk_proto_id_ = ZX_PROTOCOL_TPM; 225} 226 227Device::~Device() { 228} 229 230} // namespace tpm 231 232zx_status_t tpm_bind(void* ctx, zx_device_t* parent) { 233 zx::handle irq; 234 i2c_protocol_t i2c; 235 zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &i2c); 236 if (status != ZX_OK) { 237 zxlogf(ERROR, "tpm: could not get I2C protocol: %d\n", status); 238 return ZX_ERR_NOT_SUPPORTED; 239 } 240 241 status = i2c_get_interrupt(&i2c, 0, irq.reset_and_get_address()); 242 if (status == ZX_OK) { 243 // irq contains garbage? 244 zx_handle_t ignored __UNUSED = irq.release(); 245 } 246 247 fbl::unique_ptr<tpm::I2cCr50Interface> i2c_iface; 248 status = tpm::I2cCr50Interface::Create(parent, fbl::move(irq), &i2c_iface); 249 if (status != ZX_OK) { 250 return status; 251 } 252 253 fbl::AllocChecker ac; 254 fbl::unique_ptr<tpm::Device> device(new (&ac) tpm::Device(parent, fbl::move(i2c_iface))); 255 if (!ac.check()) { 256 return status; 257 } 258 259 status = device->Bind(); 260 if (status == ZX_OK) { 261 // DevMgr now owns this pointer, release it to avoid destroying the 262 // object when device goes out of scope. 263 __UNUSED auto ptr = device.release(); 264 } 265 return status; 266} 267