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/types.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <threads.h>
30
31 #include <utility>
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
41 namespace tpm {
42
43 // implement tpm protocol:
44
GetRandom(Device * dev,void * buf,uint16_t count,size_t * actual)45 static 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
ShutdownLocked(uint16_t type)85 zx_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
ExecuteCmd(Locality loc,const uint8_t * cmd,size_t len,uint8_t * resp,size_t max_len,size_t * actual)106 zx_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
ExecuteCmdLocked(Locality loc,const uint8_t * cmd,size_t len,uint8_t * resp,size_t max_len,size_t * actual)112 zx_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
DdkRelease()121 void Device::DdkRelease() {
122 delete this;
123 }
124
DdkSuspend(uint32_t flags)125 zx_status_t Device::DdkSuspend(uint32_t flags) {
126 fbl::AutoLock guard(&lock_);
127
128 if (flags == DEVICE_SUSPEND_FLAG_SUSPEND_RAM) {
129 zx_status_t status = ShutdownLocked(TPM_SU_STATE);
130 if (status != ZX_OK) {
131 zxlogf(ERROR, "tpm: Failed to save state: %d\n", status);
132 return status;
133 }
134 }
135
136 zx_status_t status = ReleaseLocalityLocked(0);
137 if (status != ZX_OK) {
138 zxlogf(ERROR, "tpm: Failed to release locality: %d\n", status);
139 return status;
140 }
141 return status;
142 }
143
Bind()144 zx_status_t Device::Bind() {
145 zx_status_t status = DdkAdd("tpm", DEVICE_ADD_INVISIBLE);
146 if (status != ZX_OK) {
147 return status;
148 }
149
150 thrd_t thread;
151 int ret = thrd_create_with_name(&thread, Init, this, "tpm:slow_bind");
152 if (ret != thrd_success) {
153 DdkRemove();
154 return ZX_ERR_INTERNAL;
155 }
156 thrd_detach(thread);
157 return ZX_OK;
158 }
159
Init()160 zx_status_t Device::Init() {
161 uint8_t buf[32] = { 0 };
162 size_t bytes_read;
163
164 auto cleanup = fbl::MakeAutoCall([&] {
165 DdkRemove();
166 });
167
168 zx_status_t status = iface_->Validate();
169 if (status != ZX_OK) {
170 zxlogf(TRACE, "tpm: did not pass driver validation\n");
171 return status;
172 }
173
174 {
175 fbl::AutoLock guard(&lock_);
176
177 // tpm_request_use will fail if we're not at least 30ms past _TPM_INIT.
178 // The system firmware performs the init, so it's safe to assume that
179 // is 30 ms past. If we're on systems where we need to do init,
180 // we need to wait up to 30ms for the TPM_ACCESS register to be valid.
181 status = RequestLocalityLocked(0);
182 if (status != ZX_OK) {
183 zxlogf(ERROR, "tpm: Failed to request use: %d\n", status);
184 return status;
185 }
186
187 status = WaitForLocalityLocked(0);
188 if (status != ZX_OK) {
189 zxlogf(ERROR, "tpm: Waiting for locality failed: %d\n", status);
190 return status;
191 }
192 }
193
194 DdkMakeVisible();
195
196 // Make a best-effort attempt to give the kernel some more entropy
197 // TODO(security): Perform a more recurring seeding
198 status = tpm::GetRandom(this, buf, static_cast<uint16_t>(sizeof(buf)), &bytes_read);
199 if (status == ZX_OK) {
200 zx_cprng_add_entropy(buf, bytes_read);
201 mandatory_memset(buf, 0, sizeof(buf));
202 } else {
203 zxlogf(ERROR, "tpm: Failed to add entropy to kernel CPRNG\n");
204 }
205
206 cleanup.cancel();
207 return ZX_OK;
208 }
209
Device(zx_device_t * parent,fbl::unique_ptr<HardwareInterface> iface)210 Device::Device(zx_device_t* parent, fbl::unique_ptr<HardwareInterface> iface)
211 : DeviceType(parent), iface_(std::move(iface)) {
212 ddk_proto_id_ = ZX_PROTOCOL_TPM;
213 }
214
~Device()215 Device::~Device() {
216 }
217
218 } // namespace tpm
219
tpm_bind(void * ctx,zx_device_t * parent)220 zx_status_t tpm_bind(void* ctx, zx_device_t* parent) {
221 zx::handle irq;
222 i2c_protocol_t i2c;
223 zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_I2C, &i2c);
224 if (status != ZX_OK) {
225 zxlogf(ERROR, "tpm: could not get I2C protocol: %d\n", status);
226 return ZX_ERR_NOT_SUPPORTED;
227 }
228
229 status = i2c_get_interrupt(&i2c, 0, irq.reset_and_get_address());
230 if (status == ZX_OK) {
231 // irq contains garbage?
232 zx_handle_t ignored __UNUSED = irq.release();
233 }
234
235 fbl::unique_ptr<tpm::I2cCr50Interface> i2c_iface;
236 status = tpm::I2cCr50Interface::Create(parent, std::move(irq), &i2c_iface);
237 if (status != ZX_OK) {
238 return status;
239 }
240
241 fbl::AllocChecker ac;
242 fbl::unique_ptr<tpm::Device> device(new (&ac) tpm::Device(parent, std::move(i2c_iface)));
243 if (!ac.check()) {
244 return status;
245 }
246
247 status = device->Bind();
248 if (status == ZX_OK) {
249 // DevMgr now owns this pointer, release it to avoid destroying the
250 // object when device goes out of scope.
251 __UNUSED auto ptr = device.release();
252 }
253 return status;
254 }
255