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