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