1 // Copyright 2017 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 #include "i2c-cr50.h"
6
7 #include <ddk/debug.h>
8 #include <fbl/alloc_checker.h>
9 #include <fbl/auto_lock.h>
10 #include <string.h>
11 #include <lib/zx/time.h>
12
13 #include <utility>
14
15 namespace tpm {
16
17 constexpr zx::duration I2cCr50Interface::kNoIrqTimeout;
18 constexpr zx::duration I2cCr50Interface::kI2cRetryDelay;
19 constexpr size_t kNumI2cTries = 3;
20
I2cCr50Interface(zx_device_t * i2c_dev,zx::handle irq)21 I2cCr50Interface::I2cCr50Interface(zx_device_t* i2c_dev, zx::handle irq)
22 : i2c_(i2c_dev), irq_(std::move(irq)) {
23 }
24
~I2cCr50Interface()25 I2cCr50Interface::~I2cCr50Interface() {
26 }
27
Create(zx_device_t * i2c_dev,zx::handle irq,fbl::unique_ptr<I2cCr50Interface> * out)28 zx_status_t I2cCr50Interface::Create(zx_device_t* i2c_dev, zx::handle irq,
29 fbl::unique_ptr<I2cCr50Interface>* out) {
30 fbl::AllocChecker ac;
31 fbl::unique_ptr<I2cCr50Interface> iface(new (&ac) I2cCr50Interface(i2c_dev, std::move(irq)));
32 if (!ac.check()) {
33 return ZX_ERR_NO_MEMORY;
34 }
35 *out = std::move(iface);
36 return ZX_OK;
37 }
38
Validate()39 zx_status_t I2cCr50Interface::Validate() {
40 uint16_t vid, did;
41 zx_status_t status = ReadDidVid(&did, &vid);
42 if (status != ZX_OK) {
43 return status;
44 }
45 if (vid != 0x1ae0 || did != 0x0028) {
46 return ZX_ERR_NOT_SUPPORTED;
47 }
48 return ZX_OK;
49 }
50
WaitForIrqLocked()51 zx_status_t I2cCr50Interface::WaitForIrqLocked() {
52 if (irq_) {
53 zxlogf(TRACE, "tpm: Waiting for IRQ\n");
54 zx_status_t status = zx_interrupt_wait(irq_.get(), nullptr);
55 if (status != ZX_OK) {
56 return status;
57 }
58
59 zxlogf(TRACE, "tpm: Received IRQ\n");
60 } else {
61 zx::nanosleep(zx::deadline_after(kNoIrqTimeout));
62 }
63 return ZX_OK;
64 }
65
ReadAccess(Locality loc,uint8_t * access)66 zx_status_t I2cCr50Interface::ReadAccess(Locality loc, uint8_t* access) {
67 zxlogf(TRACE, "tpm: Reading Access\n");
68 zx_status_t status = RegisterRead(RegisterAccess(loc), access);
69 zxlogf(TRACE, "tpm: Read access: %08x %d\n", *access, status);
70 return status;
71 }
72
WriteAccess(Locality loc,uint8_t access)73 zx_status_t I2cCr50Interface::WriteAccess(Locality loc, uint8_t access) {
74 zxlogf(TRACE, "tpm: Writing Access\n");
75 return RegisterWrite(RegisterAccess(loc), access);
76 }
77
ReadStatus(Locality loc,uint32_t * sts)78 zx_status_t I2cCr50Interface::ReadStatus(Locality loc, uint32_t* sts) {
79 zxlogf(TRACE, "tpm: Reading Status\n");
80 zx_status_t status = RegisterRead(RegisterStatus(loc), sts);
81 zxlogf(TRACE, "tpm: Read status: %08x %d\n", *sts, status);
82 return status;
83 }
84
WriteStatus(Locality loc,uint32_t sts)85 zx_status_t I2cCr50Interface::WriteStatus(Locality loc, uint32_t sts) {
86 zxlogf(TRACE, "tpm: Writing Status\n");
87 return RegisterWrite(RegisterStatus(loc), sts);
88 }
89
ReadDidVid(uint16_t * vid,uint16_t * did)90 zx_status_t I2cCr50Interface::ReadDidVid(uint16_t* vid, uint16_t* did) {
91 zxlogf(TRACE, "tpm: Reading DidVid\n");
92 uint32_t value;
93 zx_status_t status = RegisterRead(RegisterDidVid(0), &value);
94 if (status != ZX_OK) {
95 return status;
96 }
97 *vid = static_cast<uint16_t>(value >> 16);
98 *did = static_cast<uint16_t>(value);
99 return ZX_OK;
100 }
101
ReadDataFifo(Locality loc,uint8_t * buf,size_t len)102 zx_status_t I2cCr50Interface::ReadDataFifo(Locality loc, uint8_t* buf, size_t len) {
103 zxlogf(TRACE, "tpm: Reading %zu bytes from DataFifo\n", len);
104 return RegisterRead(RegisterDataFifo(loc), buf, len);
105 }
106
WriteDataFifo(Locality loc,const uint8_t * buf,size_t len)107 zx_status_t I2cCr50Interface::WriteDataFifo(Locality loc, const uint8_t* buf, size_t len) {
108 zxlogf(TRACE, "tpm: Writing %zu bytes to DataFifo\n", len);
109 return RegisterWrite(RegisterDataFifo(loc), buf, len);
110 }
111
I2cReadLocked(uint8_t * val,size_t len)112 zx_status_t I2cCr50Interface::I2cReadLocked(uint8_t* val, size_t len) {
113 zx_status_t status;
114 for (size_t attempt = 0; attempt < kNumI2cTries; ++attempt) {
115 if (attempt) {
116 zxlogf(TRACE, "i2c-tpm: Retrying read\n");
117 zx::nanosleep(zx::deadline_after(kI2cRetryDelay));
118 }
119
120 size_t actual;
121 status = device_read(i2c_, val, len, 0, &actual);
122 if (status == ZX_OK) {
123 if (actual != len) {
124 zxlogf(ERROR, "i2c-tpm: short read: %zu vs %zu\n", actual, len);
125 return ZX_ERR_IO;
126 }
127 break;
128 }
129 }
130 return status;
131 }
132
I2cWriteLocked(const uint8_t * val,size_t len)133 zx_status_t I2cCr50Interface::I2cWriteLocked(const uint8_t* val, size_t len) {
134 zx_status_t status;
135 for (size_t attempt = 0; attempt < kNumI2cTries; ++attempt) {
136 if (attempt) {
137 zxlogf(TRACE, "i2c-tpm: Retrying write\n");
138 zx::nanosleep(zx::deadline_after(kI2cRetryDelay));
139 }
140
141 size_t actual;
142 status = device_write(i2c_, val, len, 0, &actual);
143 if (status == ZX_OK) {
144 if (actual != len) {
145 zxlogf(ERROR, "i2c-tpm: short write: %zu vs %zu\n", actual, len);
146 return ZX_ERR_IO;
147 }
148 break;
149 }
150 }
151 return status;
152 }
153
RegisterRead(const I2cRegister<uint8_t[]> & reg,uint8_t * out,size_t len)154 zx_status_t I2cCr50Interface::RegisterRead(const I2cRegister<uint8_t[]>& reg, uint8_t* out,
155 size_t len) {
156 fbl::AutoLock guard(&lock_);
157
158 // TODO(teisenbe): Using a repeated start would be preferred here for
159 // throughput, but I2C TPM devices are not required to support it. We
160 // can test for support and use it if possible.
161
162 zx_status_t status = I2cWriteLocked(®.addr, 1);
163 if (status != ZX_OK) {
164 zxlogf(ERROR, "i2c-tpm: writing address failed\n");
165 return status;
166 }
167
168 status = WaitForIrqLocked();
169 if (status != ZX_OK) {
170 zxlogf(ERROR, "i2c-tpm: waiting for IRQ failed\n");
171 return status;
172 }
173
174 status = I2cReadLocked(out, len);
175 if (status != ZX_OK) {
176 zxlogf(ERROR, "i2c-tpm: read from %#x failed\n", reg.addr);
177 return status;
178 }
179
180 return ZX_OK;
181 }
182
RegisterWrite(const I2cRegister<uint8_t[]> & reg,const uint8_t * val,size_t len)183 zx_status_t I2cCr50Interface::RegisterWrite(const I2cRegister<uint8_t[]>& reg, const uint8_t* val,
184 size_t len) {
185 fbl::AutoLock guard(&lock_);
186
187 // TODO(teisenbe): Don't allocate here
188 size_t msg_len = len + 1;
189 fbl::unique_ptr<uint8_t[]> buf(new uint8_t[msg_len]);
190 buf[0] = reg.addr;
191 memcpy(buf.get() + 1, val, len);
192
193 zx_status_t status = I2cWriteLocked(buf.get(), msg_len);
194 if (status != ZX_OK) {
195 zxlogf(ERROR, "i2c-tpm: write to %#x failed\n", reg.addr);
196 return status;
197 }
198
199 // Wait for IRQ indicating write received
200 status = WaitForIrqLocked();
201 if (status != ZX_OK) {
202 zxlogf(ERROR, "i2c-tpm: waiting for IRQ failed\n");
203 return status;
204 }
205
206 return ZX_OK;
207 }
208
209 } // namespace tpm
210