tpm.c revision 459e772fe31a4f7c0002035bb6f69529cd2adb03
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* TPM 1.2 Driver for the TPMs that follow TIS v1.2
*/
#include "tpm_tis.h"
#include "tpm_ddi.h"
#include "tpm_duration.h"
#define TPM_HEADER_SIZE 10
typedef enum {
TPM_TAG_OFFSET = 0,
TPM_PARAMSIZE_OFFSET = 2,
TPM_RETURN_OFFSET = 6,
/*
* This is to address some TPMs that does not report the correct duration
* and timeouts. In our experience with the production TPMs, we encountered
* time errors such as GetCapability command from TPM reporting the timeout
* and durations in milliseconds rather than microseconds. Some other TPMs
* report the value 0's
*
* Short Duration is based on section 11.3.4 of TIS speciciation, that
* TPM_GetCapability (short duration) commands should not be longer than 750ms
* and that section 11.3.7 states that TPM_ContinueSelfTest (medium duration)
* should not be longer than 1 second.
*/
#define DEFAULT_SHORT_DURATION 750000
#define DEFAULT_MEDIUM_DURATION 1000000
#define DEFAULT_LONG_DURATION 300000000
#define DEFAULT_TIMEOUT_A 750000
#define DEFAULT_TIMEOUT_B 2000000
#define DEFAULT_TIMEOUT_C 750000
#define DEFAULT_TIMEOUT_D 750000
/*
* In order to test the 'millisecond bug', we test if DURATIONS and TIMEOUTS
* are unreasonably low...such as 10 milliseconds (TPM isn't that fast).
* and 400 milliseconds for long duration
*/
/*
*/
typedef enum {
TPM_CAP_RESPSIZE_OFFSET = 10,
TPM_CAP_RESP_OFFSET = 14,
typedef enum {
TPM_CAP_TIMEOUT_A_OFFSET = 14,
TPM_CAP_TIMEOUT_B_OFFSET = 18,
TPM_CAP_TIMEOUT_C_OFFSET = 22,
TPM_CAP_TIMEOUT_D_OFFSET = 26,
typedef enum {
TPM_CAP_DUR_SHORT_OFFSET = 14,
TPM_CAP_DUR_LONG_OFFSET = 22,
#define TPM_CAP_VERSION_INFO_OFFSET 14
#define TPM_CAP_VERSION_INFO_SIZE 15
/*
* Internal TPM command functions
*/
/*
* Internal TIS related functions
*/
static int tis_check_active_locality(tpm_state_t *, char);
static int tis_request_locality(tpm_state_t *, char);
static void tis_release_locality(tpm_state_t *, char, int);
static int tis_init(tpm_state_t *);
/* Auxilliary */
static inline int tpm_lock(tpm_state_t *);
static inline void tpm_unlock(tpm_state_t *);
/*
*/
/* Declaration of autoconfig functions */
static int tpm_quiesce(dev_info_t *);
/* End of autoconfig functions */
/* Declaration of driver entry point functions */
/* End of driver entry point functions */
/* cb_ops structure */
static struct cb_ops tpm_cb_ops = {
nodev, /* no strategy - nodev returns ENXIO */
nodev, /* no print */
nodev, /* no dump */
nodev, /* no ioctl */
nodev, /* no devmap */
nodev, /* no mmap */
nodev, /* no segmap */
nochpoll, /* returns ENXIO for non-pollable devices */
NULL, /* streamtab struc */
D_MP, /* compatibility flags */
CB_REV, /* cb_ops revision number */
nodev, /* no aread */
nodev /* no awrite */
};
/* dev_ops structure */
static struct dev_ops tpm_dev_ops = {
0, /* reference count */
nulldev, /* no identify - nulldev returns 0 */
nodev, /* no reset - nodev returns ENXIO */
nodev, /* no power */
};
/* modldrv structure */
&mod_driverops, /* Type: This is a driver */
"TPM 1.2 driver", /* Name of the module. */
};
/* modlinkage structure */
static struct modlinkage tpm_ml = {
&modldrv,
};
/*
* TPM commands to get the TPM's properties, e.g.,timeout
*/
/*ARGSUSED*/
static int
{
return (DDI_SUCCESS);
}
static uint32_t
{
}
/*
* Get the actual timeouts supported by the TPM by issuing TPM_GetCapability
* with the subcommand TPM_CAP_PROP_TIS_TIMEOUT
* TPM_GetCapability (TPM Main Part 3 Rev. 94, pg.38)
*/
static int
{
int ret;
/* The buffer size (30) needs room for 4 timeout values (uint32_t) */
0, 193, /* TPM_TAG_RQU_COMMAND */
0, 0, 0, 22, /* paramsize in bytes */
0, 0, 0, 101, /* TPM_ORD_GetCapability */
0, 0, 0, 5, /* TPM_CAP_Prop */
0, 0, 0, 4, /* SUB_CAP size in bytes */
0, 0, 1, 21 /* TPM_CAP_PROP_TIS_TIMEOUT(0x115) */
};
char *myname = "tpm_get_timeout";
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Get the length of the returned buffer
* Make sure that there are 4 timeout values returned
* length of the capability response is stored in data[10-13]
* Also the TPM is in network byte order
*/
"instead it's %d",
return (DDI_FAILURE);
}
/* Get the four timeout's: a,b,c,d (they are 4 bytes long each) */
if (timeout == 0) {
} else if (timeout < TEN_MILLISECONDS) {
/* timeout is in millisecond range (should be microseconds) */
timeout *= 1000;
}
if (timeout == 0) {
} else if (timeout < TEN_MILLISECONDS) {
/* timeout is in millisecond range (should be microseconds) */
timeout *= 1000;
}
if (timeout == 0) {
} else if (timeout < TEN_MILLISECONDS) {
/* timeout is in millisecond range (should be microseconds) */
timeout *= 1000;
}
if (timeout == 0) {
} else if (timeout < TEN_MILLISECONDS) {
/* timeout is in millisecond range (should be microseconds) */
timeout *= 1000;
}
return (DDI_SUCCESS);
}
/*
* Get the actual timeouts supported by the TPM by issuing TPM_GetCapability
* with the subcommand TPM_CAP_PROP_TIS_DURATION
* TPM_GetCapability (TPM Main Part 3 Rev. 94, pg.38)
*/
static int
int ret;
0, 193, /* TPM_TAG_RQU_COMMAND */
0, 0, 0, 22, /* paramsize in bytes */
0, 0, 0, 101, /* TPM_ORD_GetCapability */
0, 0, 0, 5, /* TPM_CAP_Prop */
0, 0, 0, 4, /* SUB_CAP size in bytes */
0, 0, 1, 32 /* TPM_CAP_PROP_TIS_DURATION(0x120) */
};
char *myname = "tpm_get_duration";
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Get the length of the returned buffer
* Make sure that there are 3 duration values (S,M,L: in that order)
* length of the capability response is stored in data[10-13]
* Also the TPM is in network byte order
*/
"instead, it's %d",
return (DDI_FAILURE);
}
if (duration == 0) {
} else if (duration < TEN_MILLISECONDS) {
duration *= 1000;
}
if (duration == 0) {
} else if (duration < TEN_MILLISECONDS) {
duration *= 1000;
}
if (duration == 0) {
} else if (duration < FOUR_HUNDRED_MILLISECONDS) {
duration *= 1000;
}
/* Just make the undefined duration be the same as the LONG */
return (DDI_SUCCESS);
}
/*
* Get the actual timeouts supported by the TPM by issuing TPM_GetCapability
* with the subcommand TPM_CAP_PROP_TIS_DURATION
* TPM_GetCapability (TPM Main Part 3 Rev. 94, pg.38)
*/
static int
int ret;
char vendorId[5];
/* If this buf is too small, the "vendor specific" data won't fit */
0, 193, /* TPM_TAG_RQU_COMMAND */
0, 0, 0, 18, /* paramsize in bytes */
0, 0, 0, 101, /* TPM_ORD_GetCapability */
0, 0, 0, 0x1A, /* TPM_CAP_VERSION_VAL */
0, 0, 0, 0, /* SUB_CAP size in bytes */
};
char *myname = "tpm_get_version";
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Get the length of the returned buffer.
*/
if (len < TPM_CAP_VERSION_INFO_SIZE) {
" than %d, instead, it's %d",
len);
return (DDI_FAILURE);
}
"SpecLevel %d, errataRev %d, VendorId '%s'",
vendorId);
/*
* This driver only supports TPM Version 1.2
*/
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* To prevent the TPM from complaining that certain functions are not tested
* we run this command when the driver attaches.
* For details see Section 4.2 of TPM Main Part 3 Command Specification
*/
static int
int ret;
0, 193, /* TPM_TAG_RQU COMMAND */
0, 0, 0, 10, /* paramsize in bytes */
0, 0, 0, 83 /* TPM_ORD_ContinueSelfTest */
};
char *myname = "tpm_continue_selftest";
/* Need a longer timeout */
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Auxilary Functions
*/
/*
* Find out how long we should wait for the TPM command to complete a command
*/
static clock_t
{
char *myname = "tpm_get_ordinal_duration";
/* Default and failure case for IFX */
/* Is it a TSC_ORDINAL? */
if (ordinal & TSC_ORDINAL_MASK) {
if (ordinal > TSC_ORDINAL_MAX) {
"%s: tsc ordinal: %d exceeds MAX: %d",
return (0);
}
} else {
if (ordinal > TPM_ORDINAL_MAX) {
"%s: ordinal %d exceeds MAX: %d",
return (0);
}
}
if (index > TPM_DURATION_MAX_IDX) {
return (0);
}
}
/*
* Internal TPM Transmit Function:
* Calls implementation specific sendto and receive
* The code assumes that the buffer is in network byte order
*/
static int
{
int ret;
char *myname = "itpm_command";
/* The byte order is network byte order so convert it */
if (count == 0) {
(int)bufsiz);
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/* Send the command */
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Now receive the data from the tpm
* Should at least receive "the common" 10 bytes (TPM_HEADER_SIZE)
*/
if (ret < TPM_HEADER_SIZE) {
return (DDI_FAILURE);
}
/* Check the return code */
if (ret != TPM_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Whenever the driver wants to write to the DATA_IO register, it must need
* to figure out the burstcount. This is the amount of bytes it can write
* before having to wait for long LPC bus cycle
*
* Returns: 0 if error, burst count if sucess
*/
static uint16_t
/*
* Spec says timeout should be TIMEOUT_D
* burst count is TPM_STS bits 8..23
*/
do {
/*
* burstcnt is stored as a little endian value
* 'ntohs' doesn't work since the value is not word-aligned
*/
if (burstcnt)
return (burstcnt);
} while (ddi_get_lbolt() < stop);
return (0);
}
/*
* Writing 1 to TPM_STS_CMD_READY bit in TPM_STS will do the following:
* 1. The TPM will clears IO buffers if any
* 2. The TPM will enters either Idle or Ready state within TIMEOUT_B
* (checked in the calling function)
*/
static void
}
static int
int size = 0;
int retried = 0;
/* A number of consecutive bytes that can be written to TPM */
/*
* Burstcount should be available within TIMEOUT_D
* after STS is set to valid
* burstcount is dynamic, so have to get it each time
*/
}
}
/* check to see if we need to retry (just once) */
/* issue responseRetry (TIS 1.2 pg 54) */
/* update the retry counter so we only retry once */
retried++;
/* reset the size to 0 and reread the entire response */
size = 0;
goto retry;
}
return (size);
}
/* Receive the data from the TPM */
static int
int ret;
int size = 0;
char *myname = "tis_recv_data";
if (bufsiz < TPM_HEADER_SIZE) {
/* There should be at least tag,paramsize,return code */
"the header which is %d bytes long",
goto OUT;
}
/* Read tag(2 bytes), paramsize(4), and result(4) */
if (size < TPM_HEADER_SIZE) {
goto OUT;
}
/* Get 'paramsize'(4 bytes)--it includes tag and paramsize */
"than the requested size: paramSize=%d bufsiz=%d result=%d",
goto OUT;
}
/* Read in the rest of the data from the TPM */
goto OUT;
}
/* The TPM MUST set the state to stsValid within TIMEOUT_C */
if (ret != DDI_SUCCESS) {
goto OUT;
}
/* There is still more data? */
if (status & TPM_STS_DATA_AVAIL) {
goto OUT;
}
/*
* Release the control of the TPM after we are done with it
* it...so others can also get a chance to send data
*/
OUT:
return (size);
}
/*
* Send the data (TPM commands) to the Data IO register
*/
static int
int ret;
char *myname = "tis_send_data";
if (bufsiz == 0) {
myname);
return (DDI_FAILURE);
}
/* Be in the right locality (aren't we always in locality 0?) */
"locality 0", myname);
return (DDI_FAILURE);
}
/* Put the TPM in ready state */
if (!(status & TPM_STS_CMD_READY)) {
if (ret != DDI_SUCCESS) {
"in the command ready state:"
"tpm_wait_for_stat returned error",
myname);
goto FAIL;
}
}
/*
* Now we are ready to send command
* TPM's burstcount dictates how many bytes we can write at a time
* Burstcount is dynamic if INTF_CAPABILITY for static burstcount is
* not set.
*/
if (burstcnt == 0) {
myname);
ret = DDI_FAILURE;
goto FAIL;
}
count++;
}
/* Wait for TPM to indicate that it is ready for more data */
if (ret != DDI_SUCCESS) {
"state after sending the data:", myname);
goto FAIL;
}
}
/* We can't exit the loop above unless we wrote bufsiz-1 bytes */
/* Write last byte */
count++;
/* Wait for the TPM to enter Valid State */
if (ret == DDI_FAILURE) {
goto FAIL;
}
/* The TPM should NOT be expecing more data at this point */
if ((status & TPM_STS_DATA_EXPECT) != 0) {
ret = DDI_FAILURE;
goto FAIL;
}
/*
* Final step: Writing TPM_STS_GO to TPM_STS
* register will actually send the command.
*/
/* Ordinal/Command_code is located in buf[6..9] */
if (ret == DDI_FAILURE) {
if (!(status & TPM_STS_DATA_AVAIL) ||
!(status & TPM_STS_VALID)) {
"(ordinal = %d timeout = %ld)",
} else {
"(DATA_AVAIL | VALID) failed: STS = 0x%0X",
}
goto FAIL;
}
return (DDI_SUCCESS);
FAIL:
return (ret);
}
/*
* Clear XrequestUse and Xactivelocality, where X is the current locality
*/
static void
if (force ||
== (TPM_ACCESS_REQUEST_PENDING | TPM_ACCESS_VALID)) {
/*
* Writing 1 to active locality bit in TPM_ACCESS
* register reliquishes the control of the locality
*/
}
}
/*
* Checks whether the given locality is active
* Use TPM_ACCESS register and the masks TPM_ACCESS_VALID,TPM_ACTIVE_LOCALITY
*/
static int
return (DDI_SUCCESS);
else
return (DDI_FAILURE);
}
/* Request the TPM to be in the given locality */
static int
int ret;
char *myname = "tis_request_locality";
if (ret == DDI_SUCCESS) {
/* Locality is already active */
return (DDI_SUCCESS);
}
/* Using polling */
!= DDI_SUCCESS) {
if (ddi_get_lbolt() >= timeout) {
"tis_request_locality timed out",
myname);
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/* Read the status register */
static uint8_t
}
static int
char *myname = "tpm_wait_for_stat";
/* Using polling */
if (ddi_get_lbolt() >= absolute_timeout) {
/* Timeout reached */
"polling:reached timeout",
myname);
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* Initialize TPM device
* 1. Find out supported interrupt capabilities
* 2. Set up interrupt handler if supported (some BIOSes don't support
* interrupts for TPMS, in which case we set up polling)
* 3. Determine timeouts and commands duration
*/
static int
int ret;
char *myname = "tis_init";
/*
* Temporarily set up timeouts before we get the real timeouts
* by issuing TPM_CAP commands (but to issue TPM_CAP commands,
* you need TIMEOUTs defined...chicken and egg problem here.
* TPM timeouts: Convert the milliseconds to clock cycles
*/
/*
* Do the same with the duration (real duration will be filled out
* when we call TPM_GetCapability to get the duration values from
* the TPM itself).
*/
/* Find out supported capabilities */
/* Upper 3 bytes should always return 0 */
if (intf_caps & 0x7FFFFF00) {
#ifdef DEBUG
#endif
return (DDI_FAILURE);
}
/* These two interrupts are mandatory */
if (!(intf_caps & TPM_INTF_INT_LOCALITY_CHANGE_INT)) {
"not supported", myname);
return (DDI_FAILURE);
}
if (!(intf_caps & TPM_INTF_INT_DATA_AVAIL_INT)) {
"not supported", myname);
return (DDI_FAILURE);
}
/*
* Before we start writing anything to TPM's registers,
* make sure we are in locality 0
*/
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
} /* Now we can refer to the locality as tpm->locality */
tpm->intr_enabled = 0;
/* Get the real timeouts from the TPM */
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* This gets the TPM version information */
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* Unless the TPM completes the test of its commands,
* it can return an error when the untested commands are called
*/
if (ret != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Module Entry points
*/
int
_init(void)
{
int ret;
if (ret)
return (ret);
if (ret != 0) {
return (ret);
}
return (ret);
}
int
{
int ret;
if (ret == 0)
return (ret);
}
int
_fini()
{
int ret;
if (ret != 0) {
return (ret);
}
return (ret);
}
/* End of driver configuration functions */
static int
{
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
*/
static int
{
int instance;
int nregs;
char *myname = "tpm_attach";
/* Nothing out of ordinary here */
switch (cmd) {
case DDI_ATTACH:
if (ret != DDI_SUCCESS) {
myname);
goto FAIL;
}
break;
case DDI_RESUME:
myname);
goto FAIL;
}
return (tpm_resume(tpm));
default:
ret = DDI_FAILURE;
goto FAIL;
}
/* Zeroize the flag, which is used to keep track of what is allocated */
idx = 0;
if (ret != DDI_SUCCESS)
goto FAIL;
#ifdef DEBUG
#endif
/*
* TPM vendors put the TPM registers in different
* slots in their register lists. They are not always
* the 1st set of registers, for instance.
* Loop until we find the set that matches the expected
* register size (0x5000).
*/
goto FAIL;
#ifdef DEBUG
regsize);
#endif
/* The TIS spec says the TPM registers must be 0x5000 bytes */
if (regsize == 0x5000)
break;
}
return (DDI_FAILURE);
if (ret != DDI_SUCCESS) {
goto FAIL;
}
/* Enable TPM device according to the TIS specification */
if (ret != DDI_SUCCESS) {
/* We need to clean up the ddi_regs_map_setup call */
goto FAIL;
}
/* Initialize the inter-process lock */
"pm-hardware-state", "needs-suspend-resume");
/* Initialize the buffer and the lock associated with it */
goto FAIL;
}
/* Create minor node */
DDI_PSEUDO, 0);
if (ret != DDI_SUCCESS) {
goto FAIL;
}
return (DDI_SUCCESS);
FAIL:
}
return (DDI_FAILURE);
}
/*
* Called by tpm_detach and tpm_attach (only on failure)
* Free up the resources that are allocated
*/
static void
{
return;
}
}
}
}
/* Free the mapped addresses */
}
/* Remove minor node */
}
}
static int
{
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
return (DDI_SUCCESS);
}
static int
{
char *myname = "tpm_detach";
int instance;
myname);
return (ENXIO);
}
switch (cmd) {
case DDI_DETACH:
/* Body is after the switch stmt */
break;
case DDI_SUSPEND:
return (tpm_suspend(tpm));
default:
return (DDI_FAILURE);
}
/* Since we are freeing tpm structure, we need to gain the lock */
/* Free the soft state */
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
char *myname = "tpm_getinfo";
int instance;
myname);
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
break;
case DDI_INFO_DEVT2INSTANCE:
*resultp = 0;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Driver entry points
*/
/*ARGSUSED*/
static int
{
char *myname = "tpm_open";
int instance;
myname);
return (ENXIO);
}
return (EINVAL);
}
myname);
return (EBUSY);
}
/* The device is free so mark it busy */
return (0);
}
/*ARGSUSED*/
static int
{
char *myname = "tpm_close";
int instance;
myname);
return (ENXIO);
}
return (EINVAL);
}
return (0);
}
/*ARGSUSED*/
static int
{
int ret;
char *myname = "tpm_read";
int instance;
myname);
return (ENXIO);
}
return (EFAULT);
}
/* Receive the data after requiring the lock */
/* Timeout reached */
return (ret);
"than tpm->bufsize:read in:%d, bufsiz:%d",
goto OUT;
}
if (ret < TPM_HEADER_SIZE) {
goto OUT;
}
"expected size=%d, actually read=%d",
goto OUT;
}
/* Send the buffer from the kernel to the userspace */
if (ret) {
goto OUT;
}
/* Zeroize the buffer... */
ret = DDI_SUCCESS;
OUT:
/* We are done now: wake up the waiting threads */
return (ret);
}
/*ARGSUSED*/
static int
{
int ret;
char *myname = "tpm_write";
int instance;
myname);
return (ENXIO);
}
return (EFAULT);
}
if (len == 0) {
return (0);
}
/* Get the lock for using iobuf */
/* Timeout Reached */
return (ret);
/* Copy the header and parse the structure to find out the size... */
if (ret) {
"while getting the the header",
myname);
goto OUT;
}
/* Get the buffersize from the command buffer structure */
/* Copy the command to the contiguous buffer */
"the tpm's input buffer size %d",
goto OUT;
}
/* Copy the buffer from the userspace to kernel */
if (ret) {
"while getting the rest of the command", myname);
goto OUT;
}
/* Send the command */
if (ret != DDI_SUCCESS) {
goto OUT;
}
/* Zeroize the buffer... */
ret = DDI_SUCCESS;
OUT:
return (ret);
}
/*
* This is to deal with the contentions for the iobuf
*/
static inline int
{
int ret;
/* Wait until the iobuf becomes free with the timeout */
while (tpm->iobuf_inuse) {
if (ret <= 0) {
/* Timeout reached */
return (ETIME);
}
}
return (0);
}
/*
* This is to deal with the contentions for the iobuf
*/
static inline void
{
/* Wake up the waiting threads */
tpm->iobuf_inuse = 0;
}