Stefan Thom / SPITIS_TPM20

SPITIS_TPM20.cpp

Committer:
LordOfDorks
Date:
2015-04-11
Revision:
3:4b9ad18eae02
Parent:
2:526bf792254d

File content as of revision 3:4b9ad18eae02:

/* 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"

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 = BYTE_ARRAY_TO_LEUINT32(dataRegister);

#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.InitializeTis.IntfCapability = 0x%08x\n\r", m_intfCabability);
#endif

    // Check mandatory 0 bits to see if we read a valid register
    if(m_intfCabability & 0x0FFFF800)
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;        
    }
    
    // If the TIS interface has a fixed burst count use that number instead of asking the TPM over and over and save cycles
    m_fixedBurstCount = (m_intfCabability & TIS_INTF_CAPPABILITY_BURST_COUNT_STATIC);
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.InitializeTis.FixedBurstCount = %s\n\r", m_fixedBurstCount ? "YES" : "NO");
#endif

    if((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_8B)
    {
        m_maxBurstCount = 8;
    }
    else if((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_32B)
    {
        m_maxBurstCount = 32;
    }
    else if ((m_intfCabability & TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_MASK) == TIS_INTF_CAPPABILITY_DATA_TRANSFER_SIZE_SUPPORT_64B)
    {
        m_maxBurstCount = 64;
    }
#ifdef TPM_TIS_DEBUG_OUTPUT
    printf("TIS.InitializeTis.MaxBurstCount = %d\n\r", m_maxBurstCount);
#endif
    
    result = TIS_SESSION_RESULT_COMPLETE;

Cleanup:
    ReleaseLocality();
    return result;
}

TIS_TPM20::TIS_RESULT
TIS_TPM20::StartSession(
    TIS_TPM20::TIS_LOCALITY locality
    )
{
    TIS_RESULT result = TIS_SESSION_RESULT_COMPLETE;

    // 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;
        }
    }

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

Cleanup:
    return result;
}

TIS_TPM20::TIS_RESULT
TIS_TPM20::EndSession()
{
    if(m_locality != TIS_NOT_IN_USE)
    {
        // Release the locality again
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.ReleaseLocality\r\n");
#endif
        if(!ReleaseLocality())
        {
            return TIS_SESSION_RESULT_FAILED;
        }
    }
    return TIS_SESSION_RESULT_COMPLETE;
}

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

TIS_TPM20::TIS_RESULT
TIS_TPM20::SendCommand(
    uint8_t* pbCmd,
    uint32_t cbCmd,
    uint8_t* pbTrailingData, 
    uint32_t cbTrailingData)
{
    TIS_RESULT result = TIS_SESSION_RESULT_COMPLETE;
    uint8_t tisStatus = 0;
    uint16_t burstCount = 0;
    uint32_t index = 0;
    
    // In all TPM, a buffer size of 1,024 octets is allowed.
    if(cbTrailingData > 1024)
    {
        result = TIS_SESSION_RESULT_FAILED;
        goto Cleanup;        
    }

#ifndef TPM_TIS_NO_COMMAND_FILTERING
    // Apply command filtering
    if(ApplyFilter(m_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

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

#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);

    // Submit trailing data if there is any
    if((pbTrailingData != NULL) && (cbTrailingData != 0))
    {
        index = 0;
        do
        {
            uint16_t iteration = 0;
    
            if((burstCount = GetBurstCount()) == 0)
            {
                result = TIS_SESSION_RESULT_FAILED;
                goto Cleanup;
            }
    
            // Assemble the buffer for transmission
            iteration = min((cbTrailingData - 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, &pbTrailingData[index], iteration))
            {
                result = TIS_SESSION_RESULT_FAILED;
                goto Cleanup;
            }
    
            // Update the index
            index += iteration;
        } while((cbTrailingData - 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;
    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;
    }

    // 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 = BYTE_ARRAY_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
        {
            // Calculate the response iteration size
            uint16_t iteration = min((rspSize - index), (cbRsp - index));
            if((burstCount = GetBurstCount()) == 0)
            {
                result = TIS_SESSION_RESULT_FAILED;
                goto Cleanup;               
            }
            iteration = min(iteration, burstCount);
#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))
            {
                result = TIS_SESSION_RESULT_FAILED;
                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();
    }
    return result;
}

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)
    {
        return m_maxBurstCount;
    }
    else
    {
        uint16_t burstCount = 0;
        uint8_t dataBytes[sizeof(uint16_t)] = {0};
        if(!ReadRegister(TIS_STS_BURSTCOUNT_REGISTER, dataBytes, sizeof(dataBytes)))
        {
            return 0;
        }
        burstCount = min(BYTE_ARRAY_TO_LEUINT16(dataBytes), m_maxBurstCount);
#ifdef TPM_TIS_DEBUG_OUTPUT
        printf("TIS.BurstCount = %d\r\n", burstCount);
#endif
        return burstCount;
    }
}

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)
    {
        // Reset the interrupt. Turns out the TPM needs some time to actually do this let me tell you:
        // That was a very interesting bug hunt! What we end up with here is the following - We reset
        // the interrupts now and don't look at them. While the TPM actually resets the line, we use
        // that time to send the next command but before interrupts are turned on for the next command
        // execution we have to wait until the line is back up again (see below).
        dataByte = TIS_INT_STATUS_RESET_ALL;
        if(!WriteRegister(TIS_INT_STATUS_REGISTER, &dataByte, sizeof(dataByte)))
        {
            return false;
        }
    }
    else
    {
        // Make sure the interrupt is really, really clear before we turn it back on. The TPM takes
        // around 5-10ms to transition the line back high.
#ifdef TPM_TIS_DEBUG_OUTPUT
        if(intEnable == 0) printf("TIS.InteruptStillActive\n\r");
#endif
        while(m_interrupt == 0)
        {
            wait_us(1000);
        }

        int_enable |= (uint32_t)TIS_INT_ENABLE_GLOBAL_INT_ENABLE | intEnable;
    }

    LEUINT32_TO_BYTE_ARRAY(int_enable, dataRegister);

#ifdef TPM_TIS_DEBUG_OUTPUT
    if(intEnable)
        printf("TIS.TpmInterupt = 0x%08x\n\r", intEnable);
    else
        printf("TIS.TpmInterupt = OFF\n\r");
#endif
    return(WriteRegister(TIS_INT_ENABLE_REGISTER, dataRegister, sizeof(dataRegister)));
}