/*
* Copyright ( c ) 2013 Google , Inc
*
* SPDX - License - Identifier : GPL - 2.0 +
*/
# include <common.h>
# include <dm.h>
# include <tpm.h>
# include <asm/state.h>
# include <asm/unaligned.h>
# include <linux/crc8.h>
/* TPM NVRAM location indices. */
# define FIRMWARE_NV_INDEX 0x1007
# define KERNEL_NV_INDEX 0x1008
# define NV_DATA_PUBLIC_PERMISSIONS_OFFSET 60
/* Kernel TPM space - KERNEL_NV_INDEX, locked with physical presence */
# define ROLLBACK_SPACE_KERNEL_VERSION 2
# define ROLLBACK_SPACE_KERNEL_UID 0x4752574C /* 'GRWL' */
struct rollback_space_kernel {
/* Struct version, for backwards compatibility */
uint8_t struct_version ;
/* Unique ID to detect space redefinition */
uint32_t uid ;
/* Kernel versions */
uint32_t kernel_versions ;
/* Reserved for future expansion */
uint8_t reserved [ 3 ] ;
/* Checksum (v2 and later only) */
uint8_t crc8 ;
} __packed rollback_space_kernel ;
/*
* These numbers derive from adding the sizes of command fields as shown in
* the TPM commands manual .
*/
# define TPM_REQUEST_HEADER_LENGTH 10
# define TPM_RESPONSE_HEADER_LENGTH 10
/* These are the different non-volatile spaces that we emulate */
enum {
NV_GLOBAL_LOCK ,
NV_SEQ_FIRMWARE ,
NV_SEQ_KERNEL ,
NV_SEQ_COUNT ,
} ;
/* Size of each non-volatile space */
# define NV_DATA_SIZE 0x20
/*
* Information about our TPM emulation . This is preserved in the sandbox
* state file if enabled .
*/
static struct tpm_state {
uint8_t nvdata [ NV_SEQ_COUNT ] [ NV_DATA_SIZE ] ;
} g_state ;
/**
* sandbox_tpm_read_state ( ) - read the sandbox EC state from the state file
*
* If data is available , then blob and node will provide access to it . If
* not this function sets up an empty TPM .
*
* @ blob : Pointer to device tree blob , or NULL if no data to read
* @ node : Node offset to read from
*/
static int sandbox_tpm_read_state ( const void * blob , int node )
{
const char * prop ;
int len ;
int i ;
if ( ! blob )
return 0 ;
for ( i = 0 ; i < NV_SEQ_COUNT ; i + + ) {
char prop_name [ 20 ] ;
sprintf ( prop_name , " nvdata%d " , i ) ;
prop = fdt_getprop ( blob , node , prop_name , & len ) ;
if ( prop & & len = = NV_DATA_SIZE )
memcpy ( g_state . nvdata [ i ] , prop , NV_DATA_SIZE ) ;
}
return 0 ;
}
/**
* cros_ec_write_state ( ) - Write out our state to the state file
*
* The caller will ensure that there is a node ready for the state . The node
* may already contain the old state , in which case it is overridden .
*
* @ blob : Device tree blob holding state
* @ node : Node to write our state into
*/
static int sandbox_tpm_write_state ( void * blob , int node )
{
int i ;
/*
* We are guaranteed enough space to write basic properties .
* We could use fdt_add_subnode ( ) to put each set of data in its
* own node - perhaps useful if we add access informaiton to each .
*/
for ( i = 0 ; i < NV_SEQ_COUNT ; i + + ) {
char prop_name [ 20 ] ;
sprintf ( prop_name , " nvdata%d " , i ) ;
fdt_setprop ( blob , node , prop_name , g_state . nvdata [ i ] ,
NV_DATA_SIZE ) ;
}
return 0 ;
}
SANDBOX_STATE_IO ( sandbox_tpm , " google,sandbox-tpm " , sandbox_tpm_read_state ,
sandbox_tpm_write_state ) ;
static int index_to_seq ( uint32_t index )
{
switch ( index ) {
case FIRMWARE_NV_INDEX :
return NV_SEQ_FIRMWARE ;
case KERNEL_NV_INDEX :
return NV_SEQ_KERNEL ;
case 0 :
return NV_GLOBAL_LOCK ;
}
printf ( " Invalid nv index %#x \n " , index ) ;
return - 1 ;
}
static int sandbox_tpm_xfer ( struct udevice * dev , const uint8_t * sendbuf ,
size_t send_size , uint8_t * recvbuf ,
size_t * recv_len )
{
struct tpm_state * tpm = dev_get_priv ( dev ) ;
uint32_t code , index , length , type ;
uint8_t * data ;
int seq ;
code = get_unaligned_be32 ( sendbuf + sizeof ( uint16_t ) +
sizeof ( uint32_t ) ) ;
printf ( " tpm: %zd bytes, recv_len %zd, cmd = %x \n " , send_size ,
* recv_len , code ) ;
print_buffer ( 0 , sendbuf , 1 , send_size , 0 ) ;
switch ( code ) {
case 0x65 : /* get flags */
type = get_unaligned_be32 ( sendbuf + 14 ) ;
switch ( type ) {
case 4 :
index = get_unaligned_be32 ( sendbuf + 18 ) ;
printf ( " Get flags index %#02x \n " , index ) ;
* recv_len = 22 ;
memset ( recvbuf , ' \0 ' , * recv_len ) ;
put_unaligned_be32 ( 22 , recvbuf +
TPM_RESPONSE_HEADER_LENGTH ) ;
data = recvbuf + TPM_RESPONSE_HEADER_LENGTH +
sizeof ( uint32_t ) ;
switch ( index ) {
case FIRMWARE_NV_INDEX :
break ;
case KERNEL_NV_INDEX :
/* TPM_NV_PER_PPWRITE */
put_unaligned_be32 ( 1 , data +
NV_DATA_PUBLIC_PERMISSIONS_OFFSET ) ;
break ;
}
break ;
case 0x11 : /* TPM_CAP_NV_INDEX */
index = get_unaligned_be32 ( sendbuf + 18 ) ;
printf ( " Get cap nv index %#02x \n " , index ) ;
put_unaligned_be32 ( 22 , recvbuf +
TPM_RESPONSE_HEADER_LENGTH ) ;
break ;
default :
printf ( " ** Unknown 0x65 command type %#02x \n " ,
type ) ;
return - 1 ;
}
break ;
case 0xcd : /* nvwrite */
index = get_unaligned_be32 ( sendbuf + 10 ) ;
length = get_unaligned_be32 ( sendbuf + 18 ) ;
seq = index_to_seq ( index ) ;
if ( seq < 0 )
return - 1 ;
printf ( " tpm: nvwrite index=%#02x, len=%#02x \n " , index , length ) ;
memcpy ( & tpm - > nvdata [ seq ] , sendbuf + 22 , length ) ;
* recv_len = 12 ;
memset ( recvbuf , ' \0 ' , * recv_len ) ;
break ;
case 0xcf : /* nvread */
index = get_unaligned_be32 ( sendbuf + 10 ) ;
length = get_unaligned_be32 ( sendbuf + 18 ) ;
seq = index_to_seq ( index ) ;
if ( seq < 0 )
return - 1 ;
printf ( " tpm: nvread index=%#02x, len=%#02x \n " , index , length ) ;
* recv_len = TPM_RESPONSE_HEADER_LENGTH + sizeof ( uint32_t ) +
length ;
memset ( recvbuf , ' \0 ' , * recv_len ) ;
put_unaligned_be32 ( length , recvbuf +
TPM_RESPONSE_HEADER_LENGTH ) ;
if ( seq = = NV_SEQ_KERNEL ) {
struct rollback_space_kernel rsk ;
data = recvbuf + TPM_RESPONSE_HEADER_LENGTH +
sizeof ( uint32_t ) ;
rsk . struct_version = 2 ;
rsk . uid = ROLLBACK_SPACE_KERNEL_UID ;
rsk . kernel_versions = 0 ;
rsk . crc8 = crc8 ( ( unsigned char * ) & rsk ,
offsetof ( struct rollback_space_kernel ,
crc8 ) ) ;
memcpy ( data , & rsk , sizeof ( rsk ) ) ;
} else {
memcpy ( recvbuf + TPM_RESPONSE_HEADER_LENGTH +
sizeof ( uint32_t ) , & tpm - > nvdata [ seq ] , length ) ;
}
break ;
case 0x14 : /* tpm extend */
case 0x15 : /* pcr read */
case 0x5d : /* force clear */
case 0x6f : /* physical enable */
case 0x72 : /* physical set deactivated */
case 0x99 : /* startup */
case 0x4000000a : /* assert physical presence */
* recv_len = 12 ;
memset ( recvbuf , ' \0 ' , * recv_len ) ;
break ;
default :
printf ( " Unknown tpm command %02x \n " , code ) ;
return - 1 ;
}
return 0 ;
}
static int sandbox_tpm_get_desc ( struct udevice * dev , char * buf , int size )
{
if ( size < 15 )
return - ENOSPC ;
return snprintf ( buf , size , " sandbox TPM " ) ;
}
static int sandbox_tpm_probe ( struct udevice * dev )
{
struct tpm_state * tpm = dev_get_priv ( dev ) ;
memcpy ( tpm , & g_state , sizeof ( * tpm ) ) ;
return 0 ;
}
static int sandbox_tpm_open ( struct udevice * dev )
{
return 0 ;
}
static int sandbox_tpm_close ( struct udevice * dev )
{
return 0 ;
}
static const struct tpm_ops sandbox_tpm_ops = {
. open = sandbox_tpm_open ,
. close = sandbox_tpm_close ,
. get_desc = sandbox_tpm_get_desc ,
. xfer = sandbox_tpm_xfer ,
} ;
static const struct udevice_id sandbox_tpm_ids [ ] = {
{ . compatible = " google,sandbox-tpm " } ,
{ }
} ;
U_BOOT_DRIVER ( sandbox_tpm ) = {
. name = " sandbox_tpm " ,
. id = UCLASS_TPM ,
. of_match = sandbox_tpm_ids ,
. ops = & sandbox_tpm_ops ,
. probe = sandbox_tpm_probe ,
. priv_auto_alloc_size = sizeof ( struct tpm_state ) ,
} ;