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(&reg.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