blob: ce87de17a9a8eb2d3cdeee87b1f72969161bd40a [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but without any warranty; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
/*
* The code in this file is based on the article "Writing a TPM Device Driver"
* published on http://ptgmedia.pearsoncmg.com.
*
* One principal difference is that in the simplest config the other than 0
* TPM localities do not get mapped by some devices (for instance, by Infineon
* slb9635), so this driver provides access to locality 0 only.
*/
#include <arch/io.h>
#include <config.h>
#include <endian.h>
#include <libpayload.h>
#include <stdint.h>
#include "base/cleanup_funcs.h"
#include "drivers/tpm/lpc.h"
typedef struct {
uint8_t access;
uint8_t padding0[7];
uint32_t int_enable;
uint8_t vector;
uint8_t padding1[3];
uint32_t int_status;
uint32_t int_capability;
uint8_t tpm_status;
uint8_t burst_count[2];
uint8_t padding2[9];
uint8_t data;
uint8_t padding3[3803];
uint32_t did_vid;
uint8_t rid;
uint8_t padding4[251];
} TpmLocality;
typedef struct TpmRegs
{
TpmLocality localities[5];
} TpmRegs;
enum {
TisStsValid = 0x80,
TisStsCommandReady = 0x40,
TisStsTpmGo = 0x20,
TisStsDataAvailable = 0x10,
TisStsExpect = 0x08,
TisStsResponseRetry = 0x02
};
enum {
TisAccessTpmRegValidSts = 0x80,
TisAccessActiveLocality = 0x20,
TisAccessBeenSeized = 0x10,
TisAccessSeize = 0x08,
TisAccessPendingRequest = 0x04,
TisAccessRequestUse = 0x02,
TisAccessTpmEstablishment = 0x01
};
/*
* Structures defined below allow creating descriptions of TPM vendor/device
* ID information for run time discovery. The only device the system knows
* about at this time is Infineon slb9635.
*/
struct device_name {
uint16_t dev_id;
const char * const dev_name;
};
struct vendor_name {
uint16_t vendor_id;
const char *vendor_name;
const struct device_name *dev_names;
};
static const struct device_name atmel_devices[] = {
{0x3204, "AT97SC3204"},
{0xffff}
};
static const struct device_name infineon_devices[] = {
{0x000b, "SLB9635 TT 1.2"},
{0x001a, "SLB9660 TT 1.2"},
{0x001b, "SLB9670 TT 1.2"},
{0xffff}
};
static const struct device_name nuvoton_devices[] = {
{0x00fe, "NPCT420AA V2"},
{0xffff}
};
static const struct device_name stmicro_devices[] = {
{0x0000, "ST33ZP24" },
{0xffff}
};
static const struct vendor_name vendor_names[] = {
{0x1114, "Atmel", atmel_devices},
{0x15d1, "Infineon", infineon_devices},
{0x1050, "Nuvoton", nuvoton_devices},
{0x104a, "ST Microelectronics", stmicro_devices},
};
/*
* Cached vendor/device ID pair to indicate that the device has been already
* discovered.
*/
static uint32_t vendor_dev_id;
// Retrieve burst count value.
static uint16_t burst_count(TpmLocality *dev)
{
uint16_t value;
value = readb(&dev->burst_count[0]);
value |= readb(&dev->burst_count[1]) << 8;
return value;
}
/*
* lpctpm_wait_reg()
*
* Wait for at least a second for a register to change its state to match the
* expected state. Normally the transition happens within microseconds.
*
* @reg - pointer to the TPM register
* @mask - bitmask for the bitfield(s) to watch
* @expected - value the field(s) are supposed to be set to
*
* Returns 0 on success or -1 on timeout.
*/
static int lpctpm_wait_reg(uint8_t *reg, uint8_t mask, uint8_t expected)
{
uint64_t start = timer_us(0);
do {
if ((readb(reg) & mask) == expected)
return 0;
} while (timer_us(start) < 1000 * 1000);
return -1;
}
/*
* PC Client Specific TPM Interface Specification section 11.2.12:
*
* Software must be prepared to send two writes of a "1" to command ready
* field: the first to indicate successful read of all the data, thus
* clearing the data from the ReadFIFO and freeing the TPM's resources,
* and the second to indicate to the TPM it is about to send a new command.
*
* In practice not all TPMs behave the same so it is necessary to be
* flexible when trying to set command ready.
*
* @tpm - pointer to the TPM structure
*
* Returns 0 on success if the TPM is ready for transactions.
* Returns -1 if the command ready bit does not get set.
*/
static int lpctpm_command_ready(LpcTpm *tpm)
{
uint8_t *status = &tpm->regs->localities[0].tpm_status;
// 1st attempt to set command ready.
writeb(TisStsCommandReady, status);
// Check if command ready is set yet.
if (readb(status) & TisStsCommandReady)
return 0;
// 2nd attempt to set command ready.
writeb(TisStsCommandReady, status);
// Wait for command ready to get set.
return lpctpm_wait_reg(status, TisStsCommandReady, TisStsCommandReady);
}
/*
* lpctpm_senddata()
*
* send the passed in data to the TPM device.
*
* @tpm - pointer to the TPM structure
* @data - address of the data to send, byte by byte
* @len - length of the data to send
*
* Returns 0 on success, -1 on error (in case the device does not accept the
* entire command).
*/
static uint32_t lpctpm_senddata(LpcTpm *tpm, const uint8_t * const data,
uint32_t len)
{
uint32_t offset = 0;
if (lpctpm_wait_reg(&tpm->regs->localities[0].tpm_status,
TisStsCommandReady, TisStsCommandReady)) {
printf("%s:%d - failed to get 'command_ready' status\n",
__FILE__, __LINE__);
return -1;
}
uint16_t burst = burst_count(&tpm->regs->localities[0]);
while (1) {
// Wait till the device is ready to accept more data.
uint64_t start = timer_us(0);
while (!burst) {
if (timer_us(start) > 1000 * 1000) {
printf("%s:%d failed to feed %d bytes of %d.\n",
__FILE__, __LINE__, len - offset, len);
return -1;
}
burst = burst_count(&tpm->regs->localities[0]);
}
/*
* Calculate number of bytes the TPM is ready to accept in one
* shot.
*
* We want to send the last byte outside of the loop (hence
* the -1 below) to make sure that the 'expected' status bit
* changes to zero exactly after the last byte is fed into the
* FIFO.
*/
unsigned count = MIN(burst, len - offset - 1);
while (count--)
writeb(data[offset++], &tpm->regs->localities[0].data);
if (lpctpm_wait_reg(&tpm->regs->localities[0].tpm_status,
TisStsValid, TisStsValid) ||
!(readb(&tpm->regs->localities[0].tpm_status) &
TisStsExpect)) {
printf("%s:%d TPM command feed overflow\n",
__FILE__, __LINE__);
return -1;
}
burst = burst_count(&tpm->regs->localities[0]);
if ((offset == (len - 1)) && burst) {
/*
* We need to be able to send the last byte to the
* device, so burst size must be nonzero before we
* break out.
*/
break;
}
}
// Send the last byte.
writeb(data[offset++], &tpm->regs->localities[0].data);
/*
* Verify that TPM does not expect any more data as part of this
* command.
*/
if (lpctpm_wait_reg(&tpm->regs->localities[0].tpm_status,
TisStsValid, TisStsValid) ||
(readb(&tpm->regs->localities[0].tpm_status) & TisStsExpect)) {
printf("%s:%d unexpected TPM status 0x%x\n",
__FILE__, __LINE__,
readb(&tpm->regs->localities[0].tpm_status));
return -1;
}
// OK, sitting pretty, let's start the command execution.
writeb(TisStsTpmGo, &tpm->regs->localities[0].tpm_status);
return 0;
}
/*
* lpctpm_readresponse()
*
* read the TPM device response after a command was issued.
*
* @tpm - pointer to the TPM structure
* @buffer - address where to read the response, byte by byte.
* @len - pointer to the size of buffer
*
* On success stores the number of received bytes to len and returns 0. On
* errors (misformatted TPM data or synchronization problems) returns -1.
*/
static uint32_t lpctpm_readresponse(LpcTpm *tpm, uint8_t *buffer, size_t *len)
{
uint16_t burst;
uint32_t offset = 0;
const uint32_t has_data = TisStsDataAvailable | TisStsValid;
uint32_t expected_count = *len;
// Wait for the TPM to process the command.
if (lpctpm_wait_reg(&tpm->regs->localities[0].tpm_status,
has_data, has_data)) {
printf("%s:%d failed processing command\n", __FILE__, __LINE__);
return -1;
}
do {
uint64_t start = timer_us(0);
while ((burst = burst_count(&tpm->regs->localities[0])) == 0) {
if (timer_us(start) > 1000 * 1000) {
printf("%s:%d TPM stuck on read\n",
__FILE__, __LINE__);
return -1;
}
udelay(1);
}
while (burst-- && (offset < expected_count)) {
buffer[offset++] =
readb(&tpm->regs->localities[0].data);
if (offset == 6) {
/*
* We got the first six bytes of the reply,
* let's figure out how many bytes to expect
* total - it is stored as a 4 byte number in
* network order, starting with offset 2 into
* the body of the reply.
*/
uint32_t real_length;
memcpy(&real_length,
buffer + 2,
sizeof(real_length));
expected_count = betohl(real_length);
if ((expected_count < offset) ||
(expected_count > *len)) {
printf("%s:%d bad response size %d\n",
__FILE__, __LINE__,
expected_count);
return -1;
}
}
}
// Wait for the next portion.
if (lpctpm_wait_reg(&tpm->regs->localities[0].tpm_status,
TisStsValid, TisStsValid)) {
printf("%s:%d failed to read response\n",
__FILE__, __LINE__);
return -1;
}
if (offset == expected_count)
break; // We got all we needed.
} while ((readb(&tpm->regs->localities[0].tpm_status) & has_data)
== has_data);
/*
* Make sure we indeed read all there was. The TisStsValid bit is
* known to be set.
*/
if (readb(&tpm->regs->localities[0].tpm_status) & TisStsDataAvailable) {
printf("%s:%d wrong receive status %x\n",
__FILE__, __LINE__,
readb(&tpm->regs->localities[0].tpm_status));
return -1;
}
// Tell the TPM that we are done.
if (lpctpm_command_ready(tpm) < 0)
return -1;
*len = offset;
return 0;
}
int lpctpm_close(LpcTpm *tpm)
{
uint8_t *access = &tpm->regs->localities[0].access;
if (readb(access) & TisAccessActiveLocality) {
writeb(TisAccessActiveLocality, access);
if (lpctpm_wait_reg(access, TisAccessActiveLocality, 0)) {
printf("%s:%d - failed to release locality 0\n",
__FILE__, __LINE__);
return -1;
}
}
return 0;
}
int lpctpm_close_cleanup(CleanupFunc *cleanup, CleanupType type)
{
LpcTpm *tpm = cleanup->data;
return lpctpm_close(tpm);
}
/*
* Probe the TPM device and try determining its manufacturer/device name.
*
* @tpm - pointer to the TPM structure
*
* Returns 0 on success (the device is found or was found during an earlier
* invocation) or -1 if the device is not found.
*/
int lpctpm_init(LpcTpm *tpm)
{
uint8_t *access = &tpm->regs->localities[0].access;
const char *device_name = "unknown";
const char *vendor_name = device_name;
if (vendor_dev_id)
return 0;
uint32_t didvid = readl(&tpm->regs->localities[0].did_vid);
if (!didvid || (didvid == 0xffffffff)) {
printf("%s: No TPM device found\n", __func__);
return -1;
}
vendor_dev_id = didvid;
uint16_t vid = didvid & 0xffff;
uint16_t did = (didvid >> 16) & 0xffff;
for (int i = 0; i < ARRAY_SIZE(vendor_names); i++) {
if (vid == vendor_names[i].vendor_id)
vendor_name = vendor_names[i].vendor_name;
else
continue;
for (const struct device_name *dev = vendor_names[i].dev_names;
dev->dev_id != 0xffff; dev++) {
if (dev->dev_id == did) {
device_name = dev->dev_name;
break;
}
}
break;
}
printf("Found TPM %s by %s\n", device_name, vendor_name);
if (lpctpm_close(tpm))
return -1;
// Now request access to locality.
writeb(TisAccessRequestUse, access);
// Did we get a lock?
if (lpctpm_wait_reg(access, TisAccessActiveLocality,
TisAccessActiveLocality)) {
printf("%s:%d - failed to lock locality 0\n",
__FILE__, __LINE__);
return -1;
}
// Certain TPMs need some delay here or they hang.
udelay(10);
if (lpctpm_command_ready(tpm) < 0)
return -1;
list_insert_after(&tpm->cleanup.list_node, &cleanup_funcs);
tpm->initialized = 1;
return 0;
}
int lpctpm_xmit(TpmOps *me, const uint8_t *sendbuf, size_t send_size,
uint8_t *recvbuf, size_t *recv_len)
{
LpcTpm *tpm = container_of(me, LpcTpm, ops);
if (!tpm->initialized && lpctpm_init(tpm))
return -1;
if (lpctpm_senddata(tpm, sendbuf, send_size)) {
printf("%s:%d failed sending data to TPM\n",
__FILE__, __LINE__);
return -1;
}
return lpctpm_readresponse(tpm, recvbuf, recv_len);
}
LpcTpm *new_lpc_tpm(void *addr)
{
LpcTpm *tpm = xzalloc(sizeof(*tpm));
tpm->ops.xmit = &lpctpm_xmit;
tpm->regs = addr;
tpm->cleanup.cleanup = &lpctpm_close_cleanup;
tpm->cleanup.types = CleanupOnReboot | CleanupOnPowerOff |
CleanupOnHandoff | CleanupOnLegacy;
tpm->cleanup.data = tpm;
return tpm;
}