Fix TPM driver to work with multiple vendor TPMs

Port u-boot patch for low-level driver:
- Fix bug in traversal of vendor name list.
- Sending "command ready" needs additional logic to handle
TPMs that need that bit set twice: once to empty the read
FIFOs and once to actualy set command ready.

Change-Id: I57c280266b2e966c5b90e4f9e968426a33b93cf1
Signed-off-by: Duncan Laurie <dlaurie@chromium.org>
Reviewed-on: http://review.coreboot.org/972
Tested-by: build bot (Jenkins)
This commit is contained in:
Stefan Reinauer 2012-04-30 16:33:44 -07:00 committed by Stefan Reinauer
parent 95be1d6f46
commit c908fc762c
1 changed files with 75 additions and 10 deletions

View File

@ -127,13 +127,31 @@ struct vendor_name {
const struct device_name* dev_names; const struct device_name* dev_names;
}; };
static const struct device_name atmel_devices[] = {
{0x3204, "AT97SC3204"},
{0xffff}
};
static const struct device_name infineon_devices[] = { static const struct device_name infineon_devices[] = {
{0xb, "SLB9635 TT 1.2"}, {0x000b, "SLB9635 TT 1.2"},
{0} {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[] = { static const struct vendor_name vendor_names[] = {
{0x1114, "Atmel", atmel_devices},
{0x15d1, "Infineon", infineon_devices}, {0x15d1, "Infineon", infineon_devices},
{0x1050, "Nuvoton", nuvoton_devices},
{0x104a, "ST Microelectronics", stmicro_devices},
}; };
/* /*
@ -207,6 +225,44 @@ static u32 tis_wait_reg(u8 reg, u8 locality, u8 mask, u8 expected)
return TPM_TIMEOUT_ERR; return TPM_TIMEOUT_ERR;
} }
/*
* 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.
*
* Returns 0 on success if the TPM is ready for transactions.
* Returns TPM_TIMEOUT_ERR if the command ready bit does not get set.
*/
static int tis_command_ready(u8 locality)
{
u32 status;
/* 1st attempt to set command ready */
tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS);
/* Wait for response */
status = tpm_read(locality, TIS_REG_STS);
/* Check if command ready is set yet */
if (status & TIS_STS_COMMAND_READY)
return 0;
/* 2nd attempt to set command ready */
tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS);
/* Wait for command ready to get set */
status = tis_wait_reg(TIS_REG_STS, locality,
TIS_STS_COMMAND_READY, TIS_STS_COMMAND_READY);
return (status == TPM_TIMEOUT_ERR) ? TPM_TIMEOUT_ERR : 0;
}
/* /*
* Probe the TPM device and try determining its manufacturer/device name. * Probe the TPM device and try determining its manufacturer/device name.
* *
@ -215,15 +271,17 @@ static u32 tis_wait_reg(u8 reg, u8 locality, u8 mask, u8 expected)
*/ */
static u32 tis_probe(void) static u32 tis_probe(void)
{ {
u32 didvid = tpm_read(0, TIS_REG_DID_VID);
int i;
const char *device_name = "unknown"; const char *device_name = "unknown";
const char *vendor_name = device_name; const char *vendor_name = device_name;
const struct device_name *dev;
u32 didvid;
u16 vid, did; u16 vid, did;
int i;
if (vendor_dev_id) if (vendor_dev_id)
return 0; /* Already probed. */ return 0; /* Already probed. */
didvid = tpm_read(0, TIS_REG_DID_VID);
if (!didvid || (didvid == 0xffffffff)) { if (!didvid || (didvid == 0xffffffff)) {
printf("%s: No TPM device found\n", __FUNCTION__); printf("%s: No TPM device found\n", __FUNCTION__);
return TPM_DRIVER_ERR; return TPM_DRIVER_ERR;
@ -238,11 +296,13 @@ static u32 tis_probe(void)
u16 known_did; u16 known_did;
if (vid == vendor_names[i].vendor_id) { if (vid == vendor_names[i].vendor_id) {
vendor_name = vendor_names[i].vendor_name; vendor_name = vendor_names[i].vendor_name;
} else {
continue;
} }
while ((known_did = vendor_names[i].dev_names[j].dev_id) != 0) { dev = &vendor_names[i].dev_names[j];
while ((known_did = dev->dev_id) != 0xffff) {
if (known_did == did) { if (known_did == did) {
device_name = device_name = dev->dev_name;
vendor_names[i].dev_names[j].dev_name;
break; break;
} }
j++; j++;
@ -250,7 +310,7 @@ static u32 tis_probe(void)
break; break;
} }
/* this will have to be converted into debug printout */ /* this will have to be converted into debug printout */
TPM_DEBUG("Found TPM %s by %s\n", device_name, vendor_name); printf("Found TPM %s by %s\n", device_name, vendor_name);
return 0; return 0;
} }
@ -446,7 +506,8 @@ static u32 tis_readresponse(u8 *buffer, size_t *len)
} }
/* Tell the TPM that we are done. */ /* Tell the TPM that we are done. */
tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS); if (tis_command_ready(locality) == TPM_TIMEOUT_ERR)
return TPM_DRIVER_ERR;
*len = offset; *len = offset;
return 0; return 0;
@ -492,7 +553,11 @@ int tis_open(void)
return TPM_DRIVER_ERR; return TPM_DRIVER_ERR;
} }
tpm_write(TIS_STS_COMMAND_READY, locality, TIS_REG_STS); /* Certain TPMs seem to need some delay here or they hang... */
udelay(10);
if (tis_command_ready(locality) == TPM_TIMEOUT_ERR)
return TPM_DRIVER_ERR;
return 0; return 0;
} }