Stefan Thom / SPITIS_TPM20

SPITIS_TPM20.cpp

Committer:
LordOfDorks
Date:
2015-04-07
Revision:
1:fd0a59e55a85
Parent:
0:b11c8971edd9
Child:
2:526bf792254d

File content as of revision 1:fd0a59e55a85:

/* mbed TCG SPI TPM 2.0 TIS 1.3 driver,
 * Copyright (c) 2015, Microsoft Coprporation Inc.
 * by Stefan Thom (LordOfDorks) StefanTh@Microsoft.com, Stefan@ThomsR.Us
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "SPITIS_TPM20.h"
#include "TPM20Tables.h"

TIS_TPM20::TIS_RESULT
TIS_TPM20::InitializeTis()
{
    TIS_RESULT result = TIS_SESSION_RESULT_COMPLETE;
    uint8_t dataRegister[sizeof(m_intfCabability)] = {0};
    
    if(RequestLocality(TIS_LOCALITY_0) == false)
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }
    
    // Read the TIS capabilities
    if(ReadRegister(TIS_INTF_CAPABILITY_REGISTER, dataRegister, sizeof(dataRegister)) == false)
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }
    m_intfCabability = LE_BYTEARRAY_TO_UINT32(dataRegister, 0);

#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.InitializeTis.IntfCapability = 0x%08x\n\r", m_intfCabability);
#endif
    
    // If the TIS interface has a fixed burst count use that number instead of asking the TPM over and over and save cycles
    if(m_intfCabability & TIS_INTF_CAPPABILITY_BURST_COUNT_STATIC)
    {     
        if((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_8B)
        {
            m_fixedBurstCount = 8;
        }
        else if((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_32B)
        {
            m_fixedBurstCount = 32;
        }
        else if ((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_64B)
        {
            m_fixedBurstCount = 64;
        }
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.InitializeTis.FixedBurstCount = %d\n\r", m_fixedBurstCount);
#endif
    }
    
    result = TIS_SESSION_RESULT_COMPLETE;

Cleanup:
    ReleaseLocality();
    return result;
}

#ifndef TPM_TPM_TIS_NO_COMMAND_FILTERING
TIS_TPM20::TIS_RESULT
TIS_TPM20::ApplyFilter(TIS_TPM20::TIS_LOCALITY locality, uint8_t* pbCmd, uint32_t cbCmd)
{
    uint32_t cursor = sizeof(uint16_t) + sizeof(uint32_t);
    uint32_t ordinal = BE_BYTEARRAY_TO_UINT32(pbCmd, cursor);
    int32_t handleCount = -1;

    // First filter ordinals based on locality
    if(locality == TIS_LOCALITY_0)
    {
        switch(ordinal)
        {
            // We will not allow certain from locality 0 - aka the external TPM channel
            case TPM_CC_ChangeEPS:
            case TPM_CC_ChangePPS:
            case TPM_CC_Clear:
            case TPM_CC_ClearControl:
            case TPM_CC_ClockSet:
            case TPM_CC_PCR_Allocate:
            case TPM_CC_PCR_SetAuthPolicy:
            case TPM_CC_Shutdown:
                return TIS_SESSION_RESULT_FILTERED;
            default:
                break;
        }
    }
    
    // Move on the and lets look at the handle filter
    cursor += sizeof(ordinal);
    
    // Lookup the number of handles in the command
    for(uint32_t n = 0; s_ccAttr[n].commandIndex != 0x0000 ;n++)
    {
        if(s_ccAttr[n].commandIndex == ordinal)
        {
            handleCount = s_ccAttr[n].cHandles;
            break;
        }
    }
    
    // Easy elimination of invalid cases
    if((handleCount < 0) || (handleCount > 3) ||
       (cbCmd < (cursor + (sizeof(uint32_t) * handleCount))))
    {
        return TIS_SESSION_RESULT_FILTERED;
    }
    
    // Read the handles from the command and see if they are allowed
    for(uint32_t n = 0 ;n < handleCount; n++)
    {
        uint32_t objectHandle = BE_BYTEARRAY_TO_UINT32(pbCmd, cursor);
        cursor += sizeof(objectHandle);
        
        if(locality == TIS_LOCALITY_0)
        {
            switch(objectHandle)
            {
                // We will not allow the platform entity to be used from locality 0
                case TPM_RH_PLATFORM:
                    return TIS_SESSION_RESULT_FILTERED;
                default:
                    break;
            }
        }
    }
    
    return TIS_SESSION_RESULT_COMPLETE;
}
#endif

TIS_TPM20::TIS_RESULT
TIS_TPM20::SendCommand(
    uint8_t* pbCmd,
    uint32_t cbCmd,
    TIS_TPM20::TIS_LOCALITY locality)
{
    TIS_RESULT result = TIS_SESSION_RESULT_COMPLETE;
    uint8_t tisStatus = 0;
    uint16_t burstCount = 0;
    uint32_t index = 0;

    // Is the driver busy?
    if(m_locality != TIS_NOT_IN_USE)
    {
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.Schedule: TIS busy at locality %d\n\r", m_locality);
#endif
        // Can we preemt?
        if(m_locality < locality)
        {
            result = TIS_SESSION_RESULT_OCCUPIED;
            goto Cleanup;
        }
        else
        {
            result = TIS_SESSION_RESULT_PREEMPTED;
            goto Cleanup;
        }
    }

#ifndef TPM_TIS_NO_COMMAND_FILTERING
    // Apply command filtering
    if(ApplyFilter(locality, pbCmd, cbCmd) == TIS_SESSION_RESULT_FILTERED)
    {
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.Schedule: Command filtered\n\r");
#endif
        result = TIS_SESSION_RESULT_FILTERED;
        goto Cleanup;
    }
#endif

#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.Command: ");
    for(uint32_t n = 0; n < cbCmd; n++) printf("%02x ", pbCmd[n]);
    printf("\n\r");
#endif

    // Assert the locality

    if(!RequestLocality(locality))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }

    // Make sure the TPM is ready for a command
    if(!ReadRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus)))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }

    // Get TPM ready
    if((tisStatus & TIS_STS_COMMAND_READY) == 0)
    {
        if(!AbortCommand())
        {
            result = TIS_SESSION_RESULT_FAILED;
            goto Cleanup;
        }
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.ReadyForCommand\n\r");
#endif

    // Submit Command
    do
    {
        uint16_t iteration = 0;

        if((burstCount = GetBurstCount()) == 0)
        {
            result = TIS_SESSION_RESULT_FAILED;
            goto Cleanup;
        }

        // Assemble the buffer for transmission
        iteration = min((cbCmd - index), min(burstCount, TIS_MAX_HW_FRAME_SIZE));
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.SendingBurst = %d\r\n", iteration);
#endif
        if(!WriteRegister(TIS_DATA_FIFO, &pbCmd[index], iteration))
        {
            result = TIS_SESSION_RESULT_FAILED;
            goto Cleanup;
        }

        // Update the index
        index += iteration;
    } while((cbCmd - index) > 0);

    // Command complete?
    if((!ReadRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus))) ||
       (!(tisStatus & TIS_STS_VALID) || (tisStatus & TIS_STS_DATA_EXPECT)))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.CommandComplete\n\r");
#endif

    // Arm the Interrupt
    if(!TpmInteruptOn(TIS_INT_ENABLE_DATA_AVAILABLE_INT_ENABLE))
    {
        goto Cleanup;
    }

    // Kick the command off
    tisStatus = TIS_STS_GO;
    if(!WriteRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus)))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }

#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.Go.");
#endif

    result = TIS_SESSION_RESULT_COMPLETE;

Cleanup:
    if(result != TIS_SESSION_RESULT_COMPLETE)
    {
        AbortCommand();
    }
    return result;
}

TIS_TPM20::TIS_RESULT
TIS_TPM20::RetrieveResponse(
    uint8_t* pbRsp,
    uint32_t cbRsp,
    uint32_t* pcbRsp
    )
{
    TIS_RESULT result = TIS_SESSION_RESULT_COMPLETE;
    uint8_t tisStatus = 0;
    uint16_t burstCount = 0;
    uint32_t index = 0;
    uint32_t rspSize = 0;

    if(m_interrupt != 0)
    {
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf(".");
#endif
        result = TIS_SESSION_RESULT_PENDING;
        return result;
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("IRQ!\r\n");
#endif

    // Disarm the Interrupt
    if(!TpmInteruptOn(0))
    {
        goto Cleanup;
    }

    if((!ReadRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus))) ||
       (!(tisStatus & TIS_STS_VALID) || !(tisStatus & TIS_STS_DATA_AVAIL)))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.DataAvailable\r\n");
#endif

    // Get the response header from the TPM
    if(((burstCount = GetBurstCount()) == 0) ||
       (burstCount < (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t))) ||
       (!ReadRegister(TIS_DATA_FIFO, &pbRsp[index], (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t)))))
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;
    }
    index += (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t));
    rspSize = BE_BYTEARRAY_TO_UINT32(pbRsp, sizeof(uint16_t));
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("Tis.ResponseSize = %d\r\n", rspSize);
#endif

    if(min(rspSize, cbRsp) > index)
    {
        do
        {
            uint16_t iteration = 0;

            // Check to make sure the TPM has still data for us
            if((!ReadRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus))) ||
               (!(tisStatus & TIS_STS_VALID) || !(tisStatus & TIS_STS_DATA_AVAIL))  ||
               ((burstCount = GetBurstCount()) == 0))
            {
                result = TIS_SESSION_RESULT_FAILED;
                goto Cleanup;
            }
            iteration = min((rspSize - index), min((cbRsp - index), min(burstCount, TIS_MAX_HW_FRAME_SIZE)));
#ifdef TPM_TIS_DEBUG_OUTPUT
            printf("TIS.ReceivingBurst = %d\r\n", iteration);
#endif

            // Read the data for this iteration
            if(!ReadRegister(TIS_DATA_FIFO, &pbRsp[index], iteration))
            {
                goto Cleanup;
            }
            index += iteration;
        }
        while(index < min(rspSize, cbRsp));
    }

    *pcbRsp = index;

#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.Response: ");
    for(uint32_t n = 0; n < *pcbRsp; n++) printf("%02x ", pbRsp[n]);
    printf("\n\r");
#endif

    result = TIS_SESSION_RESULT_COMPLETE;

Cleanup:
    if((result != TIS_SESSION_RESULT_COMPLETE) && (result != TIS_SESSION_RESULT_PENDING))
    {
        AbortCommand();
    }
    // Release the locality again
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.ReleaseLocality\r\n");
#endif
    ReleaseLocality();
    return result;
}

uint32_t
TIS_TPM20::ParseResponseHeader(uint8_t* pbRsp, uint32_t cbRsp, uint16_t* rspTag, uint32_t* rspSize)
{
    uint32_t rspResponseCode = 0;
    uint32_t cursor = 0;

    // Check that the response header is well formatted
    if(cbRsp < (sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t)))
    {
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.ResponseHdr: Too short = 0x%08x\n\r", cbRsp);
#endif
        rspResponseCode = TPM_RC_FAILURE;
        goto Cleanup;
    }

    // Read the header components
    *rspTag = BE_BYTEARRAY_TO_UINT16(pbRsp, cursor);
    cursor += sizeof(*rspTag);
    *rspSize = BE_BYTEARRAY_TO_UINT32(pbRsp, cursor);
    cursor += sizeof(*rspSize);
    rspResponseCode = BE_BYTEARRAY_TO_UINT32(pbRsp, cursor);
    cursor += sizeof(rspResponseCode);

    // Check the components
    if(((*rspTag != TPM_ST_NO_SESSIONS) && (*rspTag != TPM_ST_SESSIONS)) ||
       (*rspSize != cbRsp))
    {
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.ResponseHdr: Tag=0x%04x, Len=0x%08x, RC=0x%08x\n\r", *rspTag, *rspSize, rspResponseCode);
#endif
        rspResponseCode = TPM_RC_FAILURE;
        goto Cleanup;
    }

Cleanup:    
    return rspResponseCode;
}

bool
TIS_TPM20::FullDuplex(
    bool readCycle,
    uint16_t reg,
    uint8_t* pbBuffer,
    uint16_t cbBuffer
    )
{
    bool result = false;
    uint8_t dataByteIn;
    uint8_t dataByteOut;

    // Lock the bus for this operation
    m_chipSelect = 0;
    
    // Send the TIS header
    uint32_t tisHdr = TIS_HEADER(m_locality, readCycle, reg, cbBuffer);
#ifdef TPM_TIS_INTERFACE_DEBUG_OUTPUT
    printf("TIS(LC:%d,RG:%02x,SZ:%02x,%s):", m_locality, reg, cbBuffer, (readCycle) ? "RD" : "WR");
#endif

    for(uint8_t n = 0; n < sizeof(tisHdr); n++)
    {
        dataByteOut = tisHdr >> (8 * (3 - n));
        dataByteIn = m_spi.write(dataByteOut);
//#ifdef TPM_TIS_INTERFACE_DEBUG_OUTPUT
//        if(n < (sizeof(tisHdr) - 1)) printf("%02x ", dataByteOut);
//        else printf("%02x", dataByteOut);
//#endif
    }

    // The last bit we read full duplex is the first wait state indicator
    int16_t waitCycleRetry = 100;
    while(!(dataByteIn & 0x01))
    {
#ifdef TPM_TIS_INTERFACE_DEBUG_OUTPUT
        printf(".");
#endif
        // Read the next byte to see is we still have to wait
        if((dataByteIn = m_spi.write(0x00)) == 0x01)
        {
            break;
        }

        // Check the timeout
        if(waitCycleRetry-- <= 0)
        {
            result = false;
            goto Cleanup;
        }
    }

    // Full duplex the payload
    for(uint8_t n = 0; n < cbBuffer; n++)
    {
        dataByteOut = (readCycle) ? 0x00 : pbBuffer[n];
        dataByteIn = m_spi.write(dataByteOut);
        if(readCycle) pbBuffer[n] = dataByteIn;
#ifdef TPM_TIS_INTERFACE_DEBUG_OUTPUT
        printf("%02x ", (readCycle) ? dataByteIn : dataByteOut);
#endif
    }
    result = true;

Cleanup:
#ifdef TPM_TIS_INTERFACE_DEBUG_OUTPUT
    printf("\r\n");
#endif
    // Make sure to release the bus before we leave
    m_chipSelect = 1;
    return result;
}

uint16_t
TIS_TPM20::GetBurstCount()
{
    if(m_fixedBurstCount != 0)
    {
        return m_fixedBurstCount;
    }
    else
    {
        uint8_t dataBytes[sizeof(uint16_t)] = {0};
        if(!ReadRegister(TIS_STS_BURSTCOUNT_REGISTER, dataBytes, sizeof(dataBytes)))
        {
            return 0;
        }
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.BurstCount = %d\r\n", LE_BYTEARRAY_TO_UINT16(dataBytes , 0));
#endif
        return LE_BYTEARRAY_TO_UINT16(dataBytes , 0);
    }
}

bool
TIS_TPM20::RequestLocality(TIS_TPM20::TIS_LOCALITY locality)
{
    m_locality = locality;
    
    for(uint8_t n = 0; n < 100; n++)
    {
        uint8_t dataByte = 0;
        // Read a valid access register. Turns out, it may take a couple of times if the TPM was sleeping
        do
        {
            dataByte = 0;
            if(!ReadRegister(TIS_ACCESS_REGISTER, &dataByte, sizeof(dataByte)))
            {
                goto Cleanup;
            }
            
            // First time we hit that, the TPM has to wake up give it some time
            if(!(dataByte & TIS_ACCESS_VALID))
            {
                wait_us(5000);
            }
        }
        while(!(dataByte & TIS_ACCESS_VALID));

        // If we have the locality we are done
        if(dataByte & TIS_ACCESS_ACTIVE_LOCALITY)
        {
#ifdef TPM_TIS_DEBUG_OUTPUT
            printf("TIS.LocalityAquired\n\r");
#endif
            return true;
        }

        // Request the locality
        dataByte = TIS_ACCESS_REQUEST_USE;
        if(!WriteRegister(TIS_ACCESS_REGISTER, &dataByte, sizeof(dataByte)))
        {
            goto Cleanup;
        }
    }

    m_locality = TIS_NOT_IN_USE;
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.LocalityRequest = FAILED\n\r");
#endif
Cleanup:
    return false;
}

bool
TIS_TPM20::ReleaseLocality()
{
    for(uint8_t n = 0; n < 100; n++)
    {
        uint8_t dataByte = 0;
        // Read a valid access register. Turns out, it may take a couple of times if the TPM was sleeping
        do
        {
            if(!ReadRegister(TIS_ACCESS_REGISTER, &dataByte, sizeof(dataByte)))
            {
                break;
            }
 
            // First time we hit that, the TPM has to wake up give it some time
            if(!(dataByte & TIS_ACCESS_VALID))
            {
                wait_us(5000);
            }
        }
        while(!(dataByte & TIS_ACCESS_VALID));

        // If we don't have the locality we are done
        if(!(dataByte & TIS_ACCESS_ACTIVE_LOCALITY))
        {
#ifdef TPM_TIS_DEBUG_OUTPUT
            printf("TIS.LocalityReleased\n\r");
#endif
            m_locality = TIS_NOT_IN_USE;
            return true;
        }

        // Drop the locality
        dataByte = TIS_ACCESS_ACTIVE_LOCALITY;
        if(!WriteRegister(TIS_ACCESS_REGISTER, &dataByte, sizeof(dataByte)))
        {
            break;
        }
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.LocalityReleased = FAILED\n\r");
#endif

    return false;
}

bool
TIS_TPM20::TpmInteruptOn(uint32_t intEnable)
{
    uint32_t int_enable = TIS_INT_ENABLE_TYPE_POLARITY_LOW;
    uint8_t dataRegister[sizeof(uint32_t)] = {0};
    uint8_t dataByte = 0;

    if(intEnable != 0)
    {
        int_enable |= (uint32_t)TIS_INT_ENABLE_GLOBAL_INT_ENABLE | intEnable;
    }

    // Read the Interrupt state
    ReadRegister(TIS_INT_STATUS_REGISTER, &dataByte, sizeof(dataByte));

    dataByte = TIS_INT_STATUS_RESET_ALL;
    LE_UINT32_TO_BYTEARRAY(int_enable, dataRegister, 0);
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.ArmTpmInterupt = 0x%08x\n\r", intEnable);
#endif
    return ((WriteRegister(TIS_INT_STATUS_REGISTER, &dataByte, sizeof(dataByte))) &&
            (WriteRegister(TIS_INT_ENABLE_REGISTER, dataRegister, sizeof(dataRegister))));
}

bool
TIS_TPM20::AbortCommand()
{
    uint8_t tisStatus = TIS_STS_COMMAND_READY;
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.AbortCommand\n\r");
#endif
    return((WriteRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus))) &&
           (ReadRegister(TIS_STS_REGISTER, &tisStatus, sizeof(tisStatus))) &&
           ((tisStatus & TIS_STS_COMMAND_READY) != 0));
}