blob: fd6a28f9dcb446ee676de41c16361ee96b0767a7 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "i2c-cr50.h"
#include <ddk/debug.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <string.h>
#include <lib/zx/time.h>
#include <utility>
namespace tpm {
constexpr zx::duration I2cCr50Interface::kNoIrqTimeout;
constexpr zx::duration I2cCr50Interface::kI2cRetryDelay;
constexpr size_t kNumI2cTries = 3;
I2cCr50Interface::I2cCr50Interface(zx_device_t* i2c_dev, zx::handle irq)
: i2c_(i2c_dev), irq_(std::move(irq)) {
}
I2cCr50Interface::~I2cCr50Interface() {
}
zx_status_t I2cCr50Interface::Create(zx_device_t* i2c_dev, zx::handle irq,
fbl::unique_ptr<I2cCr50Interface>* out) {
fbl::AllocChecker ac;
fbl::unique_ptr<I2cCr50Interface> iface(new (&ac) I2cCr50Interface(i2c_dev, std::move(irq)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*out = std::move(iface);
return ZX_OK;
}
zx_status_t I2cCr50Interface::Validate() {
uint16_t vid, did;
zx_status_t status = ReadDidVid(&did, &vid);
if (status != ZX_OK) {
return status;
}
if (vid != 0x1ae0 || did != 0x0028) {
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t I2cCr50Interface::WaitForIrqLocked() {
if (irq_) {
zxlogf(TRACE, "tpm: Waiting for IRQ\n");
zx_status_t status = zx_interrupt_wait(irq_.get(), nullptr);
if (status != ZX_OK) {
return status;
}
zxlogf(TRACE, "tpm: Received IRQ\n");
} else {
zx::nanosleep(zx::deadline_after(kNoIrqTimeout));
}
return ZX_OK;
}
zx_status_t I2cCr50Interface::ReadAccess(Locality loc, uint8_t* access) {
zxlogf(TRACE, "tpm: Reading Access\n");
zx_status_t status = RegisterRead(RegisterAccess(loc), access);
zxlogf(TRACE, "tpm: Read access: %08x %d\n", *access, status);
return status;
}
zx_status_t I2cCr50Interface::WriteAccess(Locality loc, uint8_t access) {
zxlogf(TRACE, "tpm: Writing Access\n");
return RegisterWrite(RegisterAccess(loc), access);
}
zx_status_t I2cCr50Interface::ReadStatus(Locality loc, uint32_t* sts) {
zxlogf(TRACE, "tpm: Reading Status\n");
zx_status_t status = RegisterRead(RegisterStatus(loc), sts);
zxlogf(TRACE, "tpm: Read status: %08x %d\n", *sts, status);
return status;
}
zx_status_t I2cCr50Interface::WriteStatus(Locality loc, uint32_t sts) {
zxlogf(TRACE, "tpm: Writing Status\n");
return RegisterWrite(RegisterStatus(loc), sts);
}
zx_status_t I2cCr50Interface::ReadDidVid(uint16_t* vid, uint16_t* did) {
zxlogf(TRACE, "tpm: Reading DidVid\n");
uint32_t value;
zx_status_t status = RegisterRead(RegisterDidVid(0), &value);
if (status != ZX_OK) {
return status;
}
*vid = static_cast<uint16_t>(value >> 16);
*did = static_cast<uint16_t>(value);
return ZX_OK;
}
zx_status_t I2cCr50Interface::ReadDataFifo(Locality loc, uint8_t* buf, size_t len) {
zxlogf(TRACE, "tpm: Reading %zu bytes from DataFifo\n", len);
return RegisterRead(RegisterDataFifo(loc), buf, len);
}
zx_status_t I2cCr50Interface::WriteDataFifo(Locality loc, const uint8_t* buf, size_t len) {
zxlogf(TRACE, "tpm: Writing %zu bytes to DataFifo\n", len);
return RegisterWrite(RegisterDataFifo(loc), buf, len);
}
zx_status_t I2cCr50Interface::I2cReadLocked(uint8_t* val, size_t len) {
zx_status_t status;
for (size_t attempt = 0; attempt < kNumI2cTries; ++attempt) {
if (attempt) {
zxlogf(TRACE, "i2c-tpm: Retrying read\n");
zx::nanosleep(zx::deadline_after(kI2cRetryDelay));
}
size_t actual;
status = device_read(i2c_, val, len, 0, &actual);
if (status == ZX_OK) {
if (actual != len) {
zxlogf(ERROR, "i2c-tpm: short read: %zu vs %zu\n", actual, len);
return ZX_ERR_IO;
}
break;
}
}
return status;
}
zx_status_t I2cCr50Interface::I2cWriteLocked(const uint8_t* val, size_t len) {
zx_status_t status;
for (size_t attempt = 0; attempt < kNumI2cTries; ++attempt) {
if (attempt) {
zxlogf(TRACE, "i2c-tpm: Retrying write\n");
zx::nanosleep(zx::deadline_after(kI2cRetryDelay));
}
size_t actual;
status = device_write(i2c_, val, len, 0, &actual);
if (status == ZX_OK) {
if (actual != len) {
zxlogf(ERROR, "i2c-tpm: short write: %zu vs %zu\n", actual, len);
return ZX_ERR_IO;
}
break;
}
}
return status;
}
zx_status_t I2cCr50Interface::RegisterRead(const I2cRegister<uint8_t[]>& reg, uint8_t* out,
size_t len) {
fbl::AutoLock guard(&lock_);
// TODO(teisenbe): Using a repeated start would be preferred here for
// throughput, but I2C TPM devices are not required to support it. We
// can test for support and use it if possible.
zx_status_t status = I2cWriteLocked(&reg.addr, 1);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c-tpm: writing address failed\n");
return status;
}
status = WaitForIrqLocked();
if (status != ZX_OK) {
zxlogf(ERROR, "i2c-tpm: waiting for IRQ failed\n");
return status;
}
status = I2cReadLocked(out, len);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c-tpm: read from %#x failed\n", reg.addr);
return status;
}
return ZX_OK;
}
zx_status_t I2cCr50Interface::RegisterWrite(const I2cRegister<uint8_t[]>& reg, const uint8_t* val,
size_t len) {
fbl::AutoLock guard(&lock_);
// TODO(teisenbe): Don't allocate here
size_t msg_len = len + 1;
fbl::unique_ptr<uint8_t[]> buf(new uint8_t[msg_len]);
buf[0] = reg.addr;
memcpy(buf.get() + 1, val, len);
zx_status_t status = I2cWriteLocked(buf.get(), msg_len);
if (status != ZX_OK) {
zxlogf(ERROR, "i2c-tpm: write to %#x failed\n", reg.addr);
return status;
}
// Wait for IRQ indicating write received
status = WaitForIrqLocked();
if (status != ZX_OK) {
zxlogf(ERROR, "i2c-tpm: waiting for IRQ failed\n");
return status;
}
return ZX_OK;
}
} // namespace tpm