/*
 * tpm_20.c: TPM2.0-related support functions
 *
 * Copyright (c) 2006-2013, Intel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <config.h>
#include <types.h>
#include <stdbool.h>
#include <printk.h>
#include <misc.h>
#include <compiler.h>
#include <processor.h>
#include <io.h>
#include <string.h>
#include <tpm.h>
#include <tpm_20.h>
#include <sha1.h>
#include <sha256.h>
#include <cmdline.h>
#include <uuid.h>
#include <mle.h>
#include <loader.h>
#include <txt/acmod.h>

static u8 cmd_buf[MAX_COMMAND_SIZE];
static u8 rsp_buf[MAX_RESPONSE_SIZE];

extern loader_ctx *g_ldr_ctx;

#define reverse_copy_in(out, var) {\
    _reverse_copy((uint8_t *)(out), (uint8_t *)&(var), sizeof(var));\
    out += sizeof(var);\
}

#define reverse_copy_out(var, out) {\
    _reverse_copy((uint8_t *)&(var), (uint8_t *)(out), sizeof(var));\
    out += sizeof(var);\
}

static void reverse_copy_header(u32 cmd_code, TPM_CMD_SESSIONS_IN *sessions_in)
{
    u16 tag;

    if (sessions_in == NULL || sessions_in->num_sessions == 0)
        tag = TPM_ST_NO_SESSIONS;
    else
        tag = TPM_ST_SESSIONS;

    reverse_copy(cmd_buf, &tag, sizeof(tag));
    reverse_copy(cmd_buf + CMD_CC_OFFSET, &cmd_code, sizeof(cmd_code));
}

static void reverse_copy_pcr_selection_in(void **other, TPML_PCR_SELECTION *pcr_selection)
{
    u32 i, k;

    /* Copy count of pcrs to be read. */
    reverse_copy_in(*other, pcr_selection->count);

    for (i=0; i<pcr_selection->count; i++) {
        /* Copy alg ID for PCR to be read. */
        reverse_copy_in(*other, pcr_selection->selections[i].hash);

        /* Copy size of select array. */
        reverse_copy_in(*other, pcr_selection->selections[i].size_of_select);

        /* Copy bit field of the PCRs selected. */
        for (k=0; k<pcr_selection->selections[i].size_of_select; k++)
            reverse_copy_in(*other, pcr_selection->selections[i].pcr_select[k]);
    }
}

static void reverse_copy_pcr_selection_out(TPML_PCR_SELECTION *pcr_selection,
                                           void **other)
{
    u32 i, k;

    if (pcr_selection == NULL)
        return;

    /* Copy count of pcrs to be read. */
    reverse_copy_out(pcr_selection->count, *other);

    for (i=0; i<pcr_selection->count; i++) {
        /* Copy alg ID for PCR to be read. */
        reverse_copy_out(pcr_selection->selections[i].hash, *other);

        /* Copy size of select array. */
        reverse_copy_out(pcr_selection->selections[i].size_of_select, *other);

        /* Copy bit field of the PCRs selected */
        for (k=0; k<pcr_selection->selections[i].size_of_select; k++)
            reverse_copy_out(pcr_selection->selections[i].pcr_select[k], *other);
    }
}

/*
 * Copy sized byte buffer from source to destination and
 * twiddle the bytes in the size field.
 *
 * This can be used for the any of the following
 * TPM 2.0 data structures, but is not limited to these:
 *
 *      ENCRYPTED_SECRET_2B 
 *      TPM2B_DIGEST
 *      TPM2B_NONCE
 *      TPM2B_DATA
 *      etc. (any structures consisting of UINT16 followed by a
 *          byte buffer whose size is specified by the UINT16.
 *
 * Inputs:
 *
 *      dest -- pointer to SIZED_BYTE_BUFFER 
 *      src -- pointer to SIZED_BYTE_BUFFER 
 *
 * Outputs:
 *
 *      number of bytes copied
 */
static u16 reverse_copy_sized_buf_in(TPM2B *dest, TPM2B *src)
{
    int i;

    if (dest == NULL || src == NULL)
        return 0;

    reverse_copy(&dest->size, &src->size, sizeof(u16));
    for (i=0; i<src->size; i++)
        dest->buffer[i] = src->buffer[i];

    return sizeof(u16) + src->size;
}

static u16 reverse_copy_sized_buf_out(TPM2B *dest, TPM2B *src)
{
    int i;

    if (dest == NULL || src == NULL)
        return 0;

    reverse_copy(&dest->size, &src->size, sizeof(u16));
    for (i=0; i<dest->size; i++)
        dest->buffer[i] = src->buffer[i];

    return sizeof(u16) + dest->size;
}

static void reverse_copy_digest_out(TPML_DIGEST *tpml_digest, void **other)
{
    u32 i;

    if (tpml_digest == NULL)
        return;

    reverse_copy_out(tpml_digest->count, *other);

    for (i=0; i<tpml_digest->count; i++)
        *other += reverse_copy_sized_buf_out((TPM2B *)&(tpml_digest->digests[i]),
                (TPM2B *)*other);
}

static void reverse_copy_session_data_in(void **other,
                                         TPM_CMD_SESSION_DATA_IN *session_data,
                                         u32 *session_size)
{
    *session_size += sizeof(u32) + sizeof( u16 ) +
        session_data->nonce.t.size + sizeof( u8 ) +
        sizeof( u16 ) + session_data->hmac.t.size;

    /* copy session handle */
    reverse_copy_in(*other, session_data->session_handle);

    /* Copy nonce */
    *other += reverse_copy_sized_buf_in((TPM2B *)*other, (TPM2B *)&session_data->nonce);

    /* Copy attributes */
    *((u8 *)*other) = *(u8 *)(void *)&(session_data->session_attr);
    *other += sizeof(u8);

    /* Copy hmac data */
    *other += reverse_copy_sized_buf_in((TPM2B *)*other, (TPM2B *)&session_data->hmac);
}

static void reverse_copy_sessions_in(void **other, TPM_CMD_SESSIONS_IN *sessions_in)
{
    int i;
    u32 session_size = 0;
    void *session_size_ptr = *other;

    if (sessions_in == NULL)
        return;

    if (sessions_in->num_sessions != 0) {
        *other += sizeof(u32);
        for (i=0; i<sessions_in->num_sessions; i++)
            reverse_copy_session_data_in(other,
                    &sessions_in->sessions[i], &session_size);
    }

    reverse_copy(session_size_ptr, &session_size, sizeof(u32));
}

static void reverse_copy_session_data_out(TPM_CMD_SESSION_DATA_OUT *session_data,
                                          void **other)
{
    if (session_data == NULL)
        return;

    /* Copy nonce */
    *other += reverse_copy_sized_buf_out((TPM2B *)&(session_data->nonce),
            (TPM2B *)*other);

    /* Copy sessionAttributes */
    *(u8 *)(void *)&(session_data->session_attr) = *((u8 *)*other);
    *other += sizeof(u8);

    /* Copy hmac */
    *other += reverse_copy_sized_buf_out((TPM2B *)&(session_data->hmac),
            (TPM2B *)*other);
}

static void reverse_copy_sessions_out(TPM_CMD_SESSIONS_OUT *sessions_out,
                                      void *other, u16 rsp_tag,
                                      TPM_CMD_SESSIONS_IN *sessions_in)
{
    int i;

    if (sessions_in == NULL || sessions_out == NULL || rsp_tag != TPM_ST_SESSIONS)
        return;

    sessions_out->num_sessions = sessions_in->num_sessions;
    for (i=0; i<sessions_in->num_sessions; i++)
        reverse_copy_session_data_out(&sessions_out->sessions[i], &other);
}

typedef struct {
    u16         alg_id; 
    u16         size;  /* Size of digest */
} HASH_SIZE_INFO;

HASH_SIZE_INFO hash_sizes[] = {
    {TPM_ALG_SHA1,          SHA1_DIGEST_SIZE},
    {TPM_ALG_SHA256,        SHA256_DIGEST_SIZE},
    {TPM_ALG_SHA384,        SHA384_DIGEST_SIZE},
    {TPM_ALG_SHA512,        SHA512_DIGEST_SIZE},
    {TPM_ALG_SM3_256,       SM3_256_DIGEST_SIZE},
    {TPM_ALG_NULL,0}
};

u16 get_digest_size(u16 id)
{
    unsigned int i;
    for(i=0; i<(sizeof(hash_sizes)/sizeof(HASH_SIZE_INFO)); i++) {
        if(hash_sizes[i].alg_id == id)
            return hash_sizes[i].size;
    }

    /* If not found, return 0 size, and let TPM handle the error. */
    return 0 ;
}

static void reverse_copy_digest_value_in(void **other, TPML_DIGEST_VALUES *tpml_digest)
{
    unsigned int i, k, num_bytes;

    reverse_copy_in(*other, tpml_digest->count);

    for (i=0; i<tpml_digest->count; i++) {
        reverse_copy_in(*other, tpml_digest->digests[i].hash_alg);

        num_bytes = get_digest_size(tpml_digest->digests[i].hash_alg);

        for (k=0; k<num_bytes; k++) {
            *((u8 *)*other) = tpml_digest->digests[i].digest.sha1[k];
            *other += sizeof(u8);
        }
    }
}

static void reverse_copy_digest_values_out(TPML_DIGEST_VALUES *tpml_digest,
                                           void **other)
{
    unsigned int i, k, num_bytes;

    if (tpml_digest == NULL)
        return;

    reverse_copy_out(tpml_digest->count, *other);

    for (i=0; i<tpml_digest->count; i++) {
        reverse_copy_out(tpml_digest->digests[i].hash_alg, *other);

        num_bytes = get_digest_size(tpml_digest->digests[i].hash_alg);

        for (k=0; k<num_bytes; k++) {
            tpml_digest->digests[i].digest.sha1[k] = *((u8 *)*other);
            *other += sizeof(u8);
        }
    }
}

/*
 * Copy public data from input data structure into output data stream
 * for commands that require it.
 *
 * Inputs:
 *
 *      pointer to pointer to TPM command area to fill in with public data
 *
 *      pointer to TPM2B_PUBLIC structure
 *
 * Outputs:
 *
 *      otherData pointer points to end byte past command buffer.  This allows
 *          caller to set the commandSize field for the command.
 */
static void reverse_copy_public_in(void **other, TPM2B_PUBLIC *public)
{
    TPMT_KEYEDHASH_SCHEME  *scheme;
    TPMT_RSA_SCHEME *rsa_scheme = &(public->t.public_area.param.rsa.scheme);
    TPMT_ECC_SCHEME *ecc_scheme = &(public->t.public_area.param.ecc.scheme);
    TPMT_KDF_SCHEME *kdf = &(public->t.public_area.param.ecc.kdf);
    TPMT_ASYM_SCHEME *asym_scheme = &(public->t.public_area.param.asym.scheme);
    void *size_ptr;
    u16 size;

    size_ptr = *other;
    *other += sizeof(u16);

    reverse_copy_in(*other, public->t.public_area.type);
    reverse_copy_in(*other, public->t.public_area.name_alg);

    /* Copy public->t.object_attr */
    reverse_copy(*other, (void *)&public->t.public_area.object_attr, sizeof(u32));
    *other += sizeof(u32);

    /* Copy public->t.auth_policy */
    *other += reverse_copy_sized_buf_in((TPM2B *)*other,
            (TPM2B *)&public->t.public_area.auth_policy);

    /* Copy public->t.param */
    switch(public->t.public_area.type) {
    case TPM_ALG_KEYEDHASH:
        scheme = &(public->t.public_area.param.keyed_hash.scheme);

        reverse_copy_in(*other, scheme->scheme);

        if(scheme->scheme != TPM_ALG_NULL) {
            /* copy details */
            if(scheme->scheme == TPM_ALG_HMAC) {
                reverse_copy_in(*other, scheme->details.hmac.hash_alg);
            } else {
                reverse_copy_in(*other, scheme->details.xor.hash_alg);
                reverse_copy_in(*other, scheme->details.xor.kdf);
            }
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_in((TPM2B *)*other,
                (TPM2B *)&public->t.public_area.unique.keyed_hash);

        break;

    case TPM_ALG_SYMCIPHER:
        reverse_copy_in(*other, public->t.public_area.param.sym.alg);

        if (public->t.public_area.param.sym.alg != TPM_ALG_NULL) {
            reverse_copy_in(*other, public->t.public_area.param.sym.key_bits.sym);

            reverse_copy_in(*other, public->t.public_area.param.sym.mode.sym);
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_in((TPM2B *)*other,
                (TPM2B *)&public->t.public_area.unique.sym);

        break;

    case TPM_ALG_RSA:
        /* Copy symmetric fields */
        reverse_copy_in(*other, public->t.public_area.param.rsa.symmetric.alg);

        if (public->t.public_area.param.rsa.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_in(*other, public->t.public_area.param.rsa.symmetric.key_bits.sym);

            reverse_copy_in(*other, public->t.public_area.param.rsa.symmetric.mode.sym);
        }

        /* Copy scheme */
        reverse_copy_in(*other, rsa_scheme->scheme);
        if (rsa_scheme->scheme != TPM_ALG_NULL) {
            switch (rsa_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_in(*other, rsa_scheme->details.rsassa.hash_alg);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_in(*other, rsa_scheme->details.rsapss.hash_alg);
                break;

            case TPM_ALG_OAEP:
                reverse_copy_in(*other, rsa_scheme->details.oaep.hash_alg);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_in(*other, rsa_scheme->details.ecdsa.hash_alg);
                break;

            case TPM_ALG_SM2:
                reverse_copy_in(*other, rsa_scheme->details.sm2.hash_alg);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_in(*other, rsa_scheme->details.ecdaa.hash_alg);
                reverse_copy_in(*other, rsa_scheme->details.ecdaa.count);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_in(*other, rsa_scheme->details.ec_schnorr.hash_alg);
                break;

            default:
                reverse_copy_in(*other, rsa_scheme->details.any.hash_alg);
                break;
            }
        }

        /* Copy keybits */
        reverse_copy_in(*other, public->t.public_area.param.rsa.key_bits);

        /* Copy exponent  */
        reverse_copy_in(*other, public->t.public_area.param.rsa.exponent);

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_in((TPM2B *)*other,
                (TPM2B *)&public->t.public_area.unique.rsa);

        break;

    case TPM_ALG_ECC:
        /* Copy symmetric fields */
        reverse_copy_in(*other, public->t.public_area.param.ecc.symmetric.alg);

        if (public->t.public_area.param.ecc.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_in(*other, public->t.public_area.param.ecc.symmetric.key_bits.sym);

            reverse_copy_in(*other, public->t.public_area.param.ecc.symmetric.mode.sym);
        }

        /* Copy ECC scheme */
        reverse_copy_in(*other, ecc_scheme->scheme);
        if (ecc_scheme->scheme != TPM_ALG_NULL) {
            switch (ecc_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_in(*other, ecc_scheme->details.rsassa.hash_alg);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_in(*other, ecc_scheme->details.rsapss.hash_alg);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_in(*other, ecc_scheme->details.ecdsa.hash_alg);
                break;

            case TPM_ALG_SM2:
                reverse_copy_in(*other, ecc_scheme->details.sm2.hash_alg);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_in(*other, ecc_scheme->details.ecdaa.hash_alg);
                reverse_copy_in(*other, ecc_scheme->details.ecdaa.count);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_in(*other, ecc_scheme->details.ec_schnorr.hash_alg);
                break;

            case TPM_ALG_HMAC:
                reverse_copy_in(*other, ecc_scheme->details.hmac.hash_alg);
                break;

            default:
                reverse_copy_in(*other, ecc_scheme->details.any.hash_alg);
                break;
            }
        }

        /* Copy curve_id  */
        reverse_copy_in(*other, public->t.public_area.param.ecc.curve_id);

        /* Copy KDF scheme */
        reverse_copy_in(*other, kdf->scheme);
        switch (kdf->scheme) {
        case TPM_ALG_MGF1:
            reverse_copy_in(*other, kdf->details.mgf1.hash_alg);
            break;

        case TPM_ALG_KDF1_SP800_56a:
            reverse_copy_in(*other, kdf->details.kdf1_SP800_56a.hash_alg);
            break;

        case TPM_ALG_KDF1_SP800_108:
            reverse_copy_in(*other, kdf->details.kdf1_sp800_108.hash_alg);
            break;

        default:
            /* Copy something bogus and let TPM return error code */
            *((u16 *)*other) = 0xffff;
            *other += sizeof(u16);
            break;
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_in((TPM2B *)*other,
                (TPM2B *)&public->t.public_area.unique.ecc);

        break;

    default:
        /* Copy symmetric fields */
        reverse_copy_in(*other, public->t.public_area.param.asym.symmetric.alg);

        if (public->t.public_area.param.asym.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_in(*other, public->t.public_area.param.asym.symmetric.key_bits.sym);

            reverse_copy_in(*other, public->t.public_area.param.asym.symmetric.mode.sym);
        }

        /* Copy scheme */
        reverse_copy_in(*other, asym_scheme->scheme);
        if (asym_scheme->scheme != TPM_ALG_NULL) {
            switch (asym_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_in(*other, asym_scheme->details.rsassa.hash_alg);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_in(*other, asym_scheme->details.rsapss.hash_alg);
                break;

            case TPM_ALG_OAEP:
                reverse_copy_in(*other, asym_scheme->details.oaep.hash_alg);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_in(*other, asym_scheme->details.ecdsa.hash_alg);
                break;

            case TPM_ALG_SM2:
                reverse_copy_in(*other, asym_scheme->details.sm2.hash_alg);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_in(*other, asym_scheme->details.ecdaa.hash_alg);
                reverse_copy_in(*other, asym_scheme->details.ecdaa.count);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_in(*other, asym_scheme->details.ec_schnorr.hash_alg);
                break;

            default:
                reverse_copy_in(*other, asym_scheme->details.any.hash_alg);
                break;
            }
        }

        break;
    }

    /* Now calculate and write the inPublic size; don't include size field in the size calc */
    size = (u8 *)*other - (u8 *)size_ptr - sizeof(u16);
    reverse_copy(size_ptr, &size, sizeof(u16));
}

/*
 * Copy public data from input data structure into output data stream
 * for commands that require it.
 *
 * Inputs:
 *
 *      pointer to TPM2B_PUBLIC structure for returned data
 *
 *      pointer to pointer to TPM command byte stream for the returned data
 *
 * Outputs:
 *
 *      public contains the de-canonicalized data extracted from the output data stream
 */
static void reverse_copy_public_out(TPM2B_PUBLIC *public, void **other)
{
    TPMT_KEYEDHASH_SCHEME  *scheme;
    TPMT_RSA_SCHEME *rsa_scheme = &(public->t.public_area.param.rsa.scheme);
    TPMT_ECC_SCHEME *ecc_scheme = &(public->t.public_area.param.ecc.scheme);
    TPMT_KDF_SCHEME *kdf = &(public->t.public_area.param.ecc.kdf);
    TPMT_ASYM_SCHEME *asym_scheme = &(public->t.public_area.param.asym.scheme);

    if (public == NULL)
        return;

    reverse_copy_out(public->t.size, *other);
    reverse_copy_out(public->t.public_area.type, *other);
    reverse_copy_out(public->t.public_area.name_alg, *other);

    /* Copy public->t.object_attr */
    reverse_copy((void *)&public->t.public_area.object_attr, *other, sizeof(u32));
    *other += sizeof(u32);

    /* Copy public->t.auth_policy */
    *other += reverse_copy_sized_buf_out((TPM2B *)&public->t.public_area.auth_policy,
            (TPM2B *)*other);

    /* Copy public->t.param */
    switch(public->t.public_area.type) {
    case TPM_ALG_KEYEDHASH:
        scheme = &(public->t.public_area.param.keyed_hash.scheme);

        reverse_copy_out(scheme->scheme, *other);

        if(scheme->scheme != TPM_ALG_NULL) {
            /* copy details */
            if(scheme->scheme == TPM_ALG_HMAC) {
                reverse_copy_out(scheme->details.hmac.hash_alg, *other);
            } else {
                reverse_copy_out(scheme->details.xor.hash_alg, *other);
                reverse_copy_out(scheme->details.xor.kdf, *other);
            }
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_out((TPM2B *)&public->t.public_area.unique.keyed_hash,
                (TPM2B *)*other);

        break;

    case TPM_ALG_SYMCIPHER:
        reverse_copy_out(public->t.public_area.param.sym.alg, *other);

        if (public->t.public_area.param.sym.alg != TPM_ALG_NULL) {
            reverse_copy_out(public->t.public_area.param.sym.key_bits.sym, *other);

            reverse_copy_out(public->t.public_area.param.sym.mode.sym, *other);
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_out((TPM2B *)&public->t.public_area.unique.sym,
                (TPM2B *)*other);

        break;

    case TPM_ALG_RSA:
        /* Copy symmetric fields */
        reverse_copy_out(public->t.public_area.param.rsa.symmetric.alg, *other);

        if (public->t.public_area.param.rsa.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_out(public->t.public_area.param.rsa.symmetric.key_bits.sym, *other);

            reverse_copy_out(public->t.public_area.param.rsa.symmetric.mode.sym, *other);
        }

        /* Copy scheme */
        reverse_copy_out(rsa_scheme->scheme, *other);
        if (rsa_scheme->scheme != TPM_ALG_NULL) {
            switch (rsa_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_out(rsa_scheme->details.rsassa.hash_alg, *other);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_out(rsa_scheme->details.rsapss.hash_alg, *other);
                break;

            case TPM_ALG_OAEP:
                reverse_copy_out(rsa_scheme->details.oaep.hash_alg, *other);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_out(rsa_scheme->details.ecdsa.hash_alg, *other);
                break;

            case TPM_ALG_SM2:
                reverse_copy_out(rsa_scheme->details.sm2.hash_alg, *other);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_out(rsa_scheme->details.ecdaa.hash_alg, *other);
                reverse_copy_out(rsa_scheme->details.ecdaa.count, *other);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_out(rsa_scheme->details.ec_schnorr.hash_alg, *other);
                break;

            default:
                reverse_copy_out(rsa_scheme->details.any.hash_alg, *other);
                break;
            }
        }

        /* Copy keybits */
        reverse_copy_out(public->t.public_area.param.rsa.key_bits, *other);

        /* Copy exponent  */
        reverse_copy_out(public->t.public_area.param.rsa.exponent, *other);

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_out((TPM2B *)&public->t.public_area.unique.rsa,
                (TPM2B *)*other);

        break;

    case TPM_ALG_ECC:
        /* Copy symmetric fields */
        reverse_copy_out(public->t.public_area.param.ecc.symmetric.alg, *other);

        if (public->t.public_area.param.ecc.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_out(public->t.public_area.param.ecc.symmetric.key_bits.sym, *other);

            reverse_copy_out(public->t.public_area.param.ecc.symmetric.mode.sym, *other);
        }

        /* Copy ECC scheme */
        reverse_copy_out(ecc_scheme->scheme, *other);
        if (ecc_scheme->scheme != TPM_ALG_NULL) {
            switch (ecc_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_out(ecc_scheme->details.rsassa.hash_alg, *other);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_out(ecc_scheme->details.rsapss.hash_alg, *other);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_out(ecc_scheme->details.ecdsa.hash_alg, *other);
                break;

            case TPM_ALG_SM2:
                reverse_copy_out(ecc_scheme->details.sm2.hash_alg, *other);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_out(ecc_scheme->details.ecdaa.hash_alg, *other);
                reverse_copy_out(ecc_scheme->details.ecdaa.count, *other);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_out(ecc_scheme->details.ec_schnorr.hash_alg, *other);
                break;

            case TPM_ALG_HMAC:
                reverse_copy_out(ecc_scheme->details.hmac.hash_alg, *other);
                break;

            default:
                reverse_copy_out(ecc_scheme->details.any.hash_alg, *other);
                break;
            }
        }

        /* Copy curve_id  */
        reverse_copy_out(public->t.public_area.param.ecc.curve_id, *other);

        /* Copy KDF scheme */
        reverse_copy_out(kdf->scheme, *other);
        switch (kdf->scheme) {
        case TPM_ALG_MGF1:
            reverse_copy_out(kdf->details.mgf1.hash_alg, *other);
            break;

        case TPM_ALG_KDF1_SP800_56a:
            reverse_copy_out(kdf->details.kdf1_SP800_56a.hash_alg, *other);
            break;

        case TPM_ALG_KDF1_SP800_108:
            reverse_copy_out(kdf->details.kdf1_sp800_108.hash_alg, *other);
            break;

        default:
            /* Copy something bogus and let TPM return error code */
            *((u16 *)*other) = 0xffff;
            *other += sizeof(u16);
            break;
        }

        /* Copy public->t.public_area.unique */
        *other += reverse_copy_sized_buf_out((TPM2B *)&public->t.public_area.unique.ecc,
                (TPM2B *)*other);

        break;

    default:
        /* Copy symmetric fields */
        reverse_copy_out(public->t.public_area.param.asym.symmetric.alg, *other);

        if (public->t.public_area.param.asym.symmetric.alg != TPM_ALG_NULL) {
            reverse_copy_out(public->t.public_area.param.asym.symmetric.key_bits.sym, *other);

            reverse_copy_out(public->t.public_area.param.asym.symmetric.mode.sym, *other);
        }

        /* Copy scheme */
        reverse_copy_out(asym_scheme->scheme, *other);
        if (asym_scheme->scheme != TPM_ALG_NULL) {
            switch (asym_scheme->scheme) {
            case TPM_ALG_RSASSA:
                reverse_copy_out(asym_scheme->details.rsassa.hash_alg, *other);
                break;

            case TPM_ALG_RSAPSS:
                reverse_copy_out(asym_scheme->details.rsapss.hash_alg, *other);
                break;

            case TPM_ALG_OAEP:
                reverse_copy_out(asym_scheme->details.oaep.hash_alg, *other);
                break;

            case TPM_ALG_ECDSA:
                reverse_copy_out(asym_scheme->details.ecdsa.hash_alg, *other);
                break;

            case TPM_ALG_SM2:
                reverse_copy_out(asym_scheme->details.sm2.hash_alg, *other);
                break;

            case TPM_ALG_ECDAA:
                reverse_copy_out(asym_scheme->details.ecdaa.hash_alg, *other);
                reverse_copy_out(asym_scheme->details.ecdaa.count, *other);
                break;

            case TPM_ALG_ECSCHNORR:
                reverse_copy_out(asym_scheme->details.ec_schnorr.hash_alg, *other);
                break;

            default:
                reverse_copy_out(asym_scheme->details.any.hash_alg, *other);
                break;
            }
        }

        break;
    }
}

static void reverse_copy_creation_data_out(TPM2B_CREATION_DATA *data, void **other)
{
    if (data == NULL)
        return;

    reverse_copy_out(data->t.size, *other);

    reverse_copy_pcr_selection_out(&data->t.data.pcr_select, other);

    *other += reverse_copy_sized_buf_out((TPM2B *)&(data->t.data.pcr_digest),
            (TPM2B *)*other);

    *((u8 *)(void *)&data->t.data.locality) = *((u8 *)*other);
    *other += sizeof(u8);

    reverse_copy_out(data->t.data.parent_name_alg, *other);

    *other += reverse_copy_sized_buf_out((TPM2B *)&(data->t.data.parent_name),
            (TPM2B *)*other);
    *other += reverse_copy_sized_buf_out((TPM2B *)&(data->t.data.parent_qualified_name),
            (TPM2B *)*other);
    *other += reverse_copy_sized_buf_out((TPM2B *)&(data->t.data.outside_info),
            (TPM2B *)*other);
}

static void reverse_copy_ticket_out(TPMT_TK_CREATION *ticket, void **other)
{
    if (ticket == NULL)
        return;

    reverse_copy_out(ticket->tag, *other);

    reverse_copy_out(ticket->hierarchy, *other);

    *other += reverse_copy_sized_buf_out((TPM2B *)&(ticket->digest), (TPM2B *)*other);
}

static uint32_t _tpm20_pcr_read(u32 locality,
                                tpm_pcr_read_in *in,
                                tpm_pcr_read_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_PCR_Read, 0);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_pcr_selection_in(&other, &in->pcr_selection);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))  
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));

    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32); /* Skip past parameter size field */

    reverse_copy_out(out->pcr_update_counter, other);

    reverse_copy_pcr_selection_out(&out->pcr_selection, &other);

    reverse_copy_digest_out(&out->pcr_values, &other);

    return ret;
}

static uint32_t _tpm20_pcr_extend(uint32_t locality,
                                  tpm_pcr_extend_in *in,
                                  tpm_pcr_extend_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_PCR_Extend, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->pcr_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    reverse_copy_digest_value_in(&other, &in->digests);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_pcr_event(uint32_t locality,
                                 tpm_pcr_event_in *in,
                                 tpm_pcr_event_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_PCR_Event, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->pcr_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->data));

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
    
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_digest_values_out(&out->digests, &other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_pcr_reset(uint32_t locality,
                                 tpm_pcr_reset_in *in,
                                 tpm_pcr_reset_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_PCR_Reset, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy(other, &in->pcr_handle, sizeof(u32));

    other += sizeof(u32);
    reverse_copy_sessions_in(&other, &in->sessions);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_sequence_start(uint32_t locality,
                                      tpm_sequence_start_in *in,
                                      tpm_sequence_start_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_HashSequenceStart, 0);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->auth));

    reverse_copy_in(other, in->hash_alg);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = RSP_HEAD_SIZE + sizeof(*out);
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_out(out->handle, other);

    return ret;
}

static uint32_t _tpm20_sequence_update(uint32_t locality,
                                       tpm_sequence_update_in *in,
                                       tpm_sequence_update_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_SequenceUpdate, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;

    reverse_copy_in(other, in->handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->buf));

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_sequence_complete(uint32_t locality,
                                         tpm_sequence_complete_in *in,
                                         tpm_sequence_complete_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_EventSequenceComplete, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;

    reverse_copy_in(other, in->pcr_handle);
    reverse_copy_in(other, in->seq_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->buf));

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_digest_values_out(&out->results, &other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_nv_read(uint32_t locality,
                               tpm_nv_read_in *in,
                               tpm_nv_read_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_NV_Read, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->handle);
    reverse_copy_in(other, in->index);

    reverse_copy_sessions_in(&other, &in->sessions);

    reverse_copy_in(other, in->size);
    reverse_copy_in(other, in->offset);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
 
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    other += reverse_copy_sized_buf_out((TPM2B *)&(out->data), (TPM2B *)other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_nv_write(uint32_t locality,
                                tpm_nv_write_in *in,
                                tpm_nv_write_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_NV_Write, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->handle);
    reverse_copy_in(other, in->index);

    reverse_copy_sessions_in(&other, &in->sessions);

    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->data));

    reverse_copy_in(other, in->offset);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_nv_read_public(uint32_t locality,
                                      tpm_nv_read_public_in *in,
                                      tpm_nv_read_public_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_NV_ReadPublic, 0);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->index);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    reverse_copy_out(out->nv_public.t.size, other);
    reverse_copy_out(out->nv_public.t.nv_public.index, other);
    reverse_copy_out(out->nv_public.t.nv_public.name_alg, other);
    reverse_copy((void *)&(out->nv_public.t.nv_public.attr), other, sizeof(u32));
    other += sizeof(u32);
    other += reverse_copy_sized_buf_out((TPM2B *)&(out->nv_public.t.nv_public.auth_policy),
            (TPM2B *)other);
    reverse_copy_out(out->nv_public.t.nv_public.data_size, other);
 
    other += reverse_copy_sized_buf_out((TPM2B *)&(out->nv_name), (TPM2B *)other);

    return ret;
}

static uint32_t _tpm20_get_random(uint32_t locality,
                                 tpm_get_random_in *in,
                                 tpm_get_random_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_GetRandom, 0);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->bytes_req);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
 
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    other += reverse_copy_sized_buf_out((TPM2B *)&(out->random_bytes), (TPM2B *)other);

    return ret;
}

static uint32_t _tpm20_shutdown(uint32_t locality, u16 type)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    void *other;
    
    reverse_copy_header(TPM_CC_Shutdown, 0);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, type);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = RSP_HEAD_SIZE; 

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    return ret;
}

static __data u32 handle2048 = 0;
static const char auth_str[] = "test";
static uint32_t _tpm20_create_primary(uint32_t locality,
                                     tpm_create_primary_in *in,
                                     tpm_create_primary_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    u16 sensitive_size;
    void *sensitive_size_ptr;
    void *other;
    
    reverse_copy_header(TPM_CC_CreatePrimary, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->primary_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    /* Copy inSensitive */
    sensitive_size_ptr = other;
    other += sizeof(u16);
    other += reverse_copy_sized_buf_in((TPM2B *)other,
            (TPM2B *)&(in->sensitive.t.sensitive.user_auth));
    other += reverse_copy_sized_buf_in((TPM2B *)other,
            (TPM2B *)&(in->sensitive.t.sensitive.data));
    sensitive_size = (u8 *)other - (u8 *)sensitive_size_ptr - sizeof(u16);
    reverse_copy(sensitive_size_ptr, &sensitive_size, sizeof(u16));

    /* Copy inPublic */
    reverse_copy_public_in(&other, &in->public);

    /* Copy outsideInfo */
    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->outside_info));

    /* Copy creationPCR */
    reverse_copy_pcr_selection_in(&other, &in->creation_pcr);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }


    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;

    /* Save objHandle */
    reverse_copy_out(out->obj_handle, other);

    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    /* Save outPublic */
    reverse_copy_public_out(&out->public, &other);

    /* Save creationData */
    reverse_copy_creation_data_out(&(out->creation_data), &other);

    /* Save creationHash */
    other += reverse_copy_sized_buf_out((TPM2B *)&(out->creation_hash),
            (TPM2B *)other);

    /* Save creationTicket */
    reverse_copy_ticket_out(&(out->creation_ticket), &other);

    other += reverse_copy_sized_buf_out((TPM2B *)&(out->name), (TPM2B *)other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}


static uint32_t _tpm20_create(uint32_t locality,
                              tpm_create_in *in,
                              tpm_create_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    u16 sensitive_size;
    void *sensitive_size_ptr;
    void *other;
    
    reverse_copy_header(TPM_CC_Create, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->parent_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    /* Copy inSensitive */
    sensitive_size_ptr = other;
    other += sizeof(u16);
    other += reverse_copy_sized_buf_in((TPM2B *)other,
            (TPM2B *)&(in->sensitive.t.sensitive.user_auth));
    other += reverse_copy_sized_buf_in((TPM2B *)other,
            (TPM2B *)&(in->sensitive.t.sensitive.data));
    sensitive_size = (u8 *)other - (u8 *)sensitive_size_ptr - sizeof(u16);
    reverse_copy(sensitive_size_ptr, &sensitive_size, sizeof(u16));

    /* Copy inPublic */
    reverse_copy_public_in(&other, &in->public);

    /* Copy outsideInfo */
    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->outside_info));

    /* Copy creationPCR */
    reverse_copy_pcr_selection_in(&other, &in->creation_pcr);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);
    
    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    /* Save outPrivate */
    other += reverse_copy_sized_buf_out((TPM2B *)&(out->private), (TPM2B *)other);

    /* Save outPublic */
    reverse_copy_public_out(&out->public, &other);

    /* Save creationData */
    reverse_copy_creation_data_out(&(out->creation_data), &other);

    /* Save creationHash */
    other += reverse_copy_sized_buf_out((TPM2B *)&(out->creation_hash),
            (TPM2B *)other);

    /* Save creationTicket */
    reverse_copy_ticket_out(&(out->creation_ticket), &other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_load(uint32_t locality,
                            tpm_load_in *in,
                            tpm_load_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_Load, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->parent_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    other += reverse_copy_sized_buf_in((TPM2B *)other, (TPM2B *)&(in->private));

    reverse_copy_public_in(&other, &in->public);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;

    reverse_copy_out(out->obj_handle, other);

    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    other += reverse_copy_sized_buf_out((TPM2B *)&(out->name), (TPM2B *)other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

static uint32_t _tpm20_unseal(uint32_t locality,
                             tpm_unseal_in *in,
                             tpm_unseal_out *out)
{
    u32 ret;
    u32 cmd_size, rsp_size;
    u16 rsp_tag;
    void *other;
    
    reverse_copy_header(TPM_CC_Unseal, &in->sessions);

    other = (void *)cmd_buf + CMD_HEAD_SIZE;
    reverse_copy_in(other, in->item_handle);

    reverse_copy_sessions_in(&other, &in->sessions);

    /* Now set the command size field, now that we know the size of the whole command */
    cmd_size = (u8 *)other - cmd_buf;
    reverse_copy(cmd_buf + CMD_SIZE_OFFSET, &cmd_size, sizeof(cmd_size));

    rsp_size = sizeof(*out);

    if (g_tpm_family == TPM_IF_20_FIFO) {
        if (!tpm_submit_cmd(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }
    if (g_tpm_family == TPM_IF_20_CRB) {
        if (!tpm_submit_cmd_crb(locality, cmd_buf, cmd_size, rsp_buf, &rsp_size))
            return TPM_RC_FAILURE;
        }

    reverse_copy(&ret, rsp_buf + RSP_RST_OFFSET, sizeof(ret));
    if ( ret != TPM_RC_SUCCESS )
        return ret;

    other = (void *)rsp_buf + RSP_HEAD_SIZE;
    reverse_copy(&rsp_tag, rsp_buf, sizeof(rsp_tag));
    if (rsp_tag == TPM_ST_SESSIONS)
        other += sizeof(u32);

    other += reverse_copy_sized_buf_out((TPM2B *)&(out->data), (TPM2B *)other);

    reverse_copy_sessions_out(&out->sessions, other, rsp_tag, &in->sessions);

    return ret;
}

TPM_CMD_SESSION_DATA_IN pw_session;
static void create_pw_session(TPM_CMD_SESSION_DATA_IN *ses)
{
    ses->session_handle = TPM_RS_PW;
    ses->nonce.t.size = 0;
    *((u8 *)((void *)&ses->session_attr)) = 0;
    ses->hmac.t.size = 0;
}

#define SET_PCR_SELECT_BIT( pcr_selection, pcr ) \
    (pcr_selection).pcr_select[( (pcr)/8 )] |= ( 1 << ( (pcr) % 8) );
static bool tpm20_pcr_read(struct tpm_if *ti, uint32_t locality,
                           uint32_t pcr, tpm_pcr_value_t *out)
{
    tpm_pcr_read_in read_in;
    tpm_pcr_read_out read_out;
    u32 ret;

    if ( ti == NULL || out == NULL )
        return false;

    read_in.pcr_selection.count = 1;
    read_in.pcr_selection.selections[0].hash = ti->cur_alg;
    read_in.pcr_selection.selections[0].size_of_select = 3;
    read_in.pcr_selection.selections[0].pcr_select[0] = 0;
    read_in.pcr_selection.selections[0].pcr_select[1] = 0;
    read_in.pcr_selection.selections[0].pcr_select[2] = 0;
    SET_PCR_SELECT_BIT( read_in.pcr_selection.selections[0], pcr );

    ret = _tpm20_pcr_read(locality, &read_in, &read_out);
    if (ret != TPM_RC_SUCCESS) {
        printk(TBOOT_WARN"TPM: Pcr %d Read return value = %08X\n", pcr, ret);
        ti->error = ret;
        return false;
    }

    copy_hash(out,
            (tb_hash_t *)&(read_out.pcr_values.digests[0].t.buffer[0]),
            ti->cur_alg);

    return true;
}

static bool tpm20_pcr_extend(struct tpm_if *ti, uint32_t locality,
                             uint32_t pcr, const hash_list_t *in)
{
    tpm_pcr_extend_in extend_in;
    tpm_pcr_extend_out extend_out;
    u32 ret, i;

    if ( ti == NULL || in == NULL )
        return false;

    extend_in.pcr_handle = pcr;
    extend_in.sessions.num_sessions = 1;
    extend_in.sessions.sessions[0] = pw_session;

    extend_in.digests.count = in->count;
    for (i=0; i<in->count; i++) {
        extend_in.digests.digests[i].hash_alg = in->entries[i].alg;
        copy_hash((tb_hash_t *)&extend_in.digests.digests[i].digest,
                &in->entries[i].hash, in->entries[i].alg);
    }

    ret = _tpm20_pcr_extend(locality, &extend_in, &extend_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: Pcr %d extend, return value = %08X\n", pcr, ret);
        ti->error = ret;
        return false;
    }

    return true;
}

static bool tpm20_pcr_reset(struct tpm_if *ti, uint32_t locality, uint32_t pcr)
{
    tpm_pcr_reset_in reset_in;
    tpm_pcr_reset_out reset_out;
    u32 ret;

    reset_in.pcr_handle = pcr;
    reset_in.sessions.num_sessions = 1;
    reset_in.sessions.sessions[0] = pw_session;

    ret = _tpm20_pcr_reset(locality, &reset_in, &reset_out);
    if (ret != TPM_RC_SUCCESS) {
        printk(TBOOT_WARN"TPM: Pcr %d Reset return value = %08X\n", pcr, ret);
        ti->error = ret;
        return false;
    }

    return true;
}

static bool tpm20_hash(struct tpm_if *ti, u32 locality, const u8 *data,
                       u32 data_size, hash_list_t *hl)
{
    tpm_sequence_start_in start_in;
    tpm_sequence_start_out start_out;
    tpm_sequence_update_in update_in;
    tpm_sequence_update_out update_out;
    tpm_sequence_complete_in complete_in;
    tpm_sequence_complete_out complete_out;
    TPM2B_MAX_BUFFER buffer;
    u32 ret, i, j, chunk_size;

    if ( ti == NULL || data == NULL || data_size == 0 )
        return false;

    start_in.auth.t.size = 2;
    start_in.auth.t.buffer[0] = 0;
    start_in.auth.t.buffer[1] = 0xff;
    start_in.hash_alg = TPM_ALG_NULL;

    ret = _tpm20_sequence_start(locality, &start_in, &start_out);
    if (ret != TPM_RC_SUCCESS) {
        printk(TBOOT_WARN"TPM: HashSequenceStart return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }

    update_in.sessions.num_sessions = 1;
    update_in.sessions.sessions[0] = pw_session;
    update_in.sessions.sessions[0].hmac = start_in.auth;
    update_in.handle = start_out.handle;

    complete_in.pcr_handle = TPM_RH_NULL;
    complete_in.seq_handle = start_out.handle;
    complete_in.sessions.num_sessions = 2;
    create_pw_session(&complete_in.sessions.sessions[0]);
    complete_in.sessions.sessions[1] = pw_session;
    complete_in.sessions.sessions[1].hmac = start_in.auth;

    for( i=0; i<data_size; i+=chunk_size ) {
        if( (data_size-i) > MAX_DIGEST_BUFFER ) {
            chunk_size = 1024;
        } else {
            chunk_size = data_size - i;
        }

        buffer.t.size = chunk_size;
        memcpy( &(buffer.t.buffer[0]), &(data[i] ), chunk_size );

        if( chunk_size == 1024 ) {
            update_in.buf = buffer;
            ret = _tpm20_sequence_update(locality, &update_in, &update_out);
            if (ret != TPM_RC_SUCCESS) {
                printk(TBOOT_WARN"TPM: SequenceUpdate return value = %08X\n", ret);
                ti->error = ret;
                return false;
            }
        } else {
            complete_in.buf = buffer;
            ret = _tpm20_sequence_complete(locality, &complete_in, &complete_out);
            if (ret != TPM_RC_SUCCESS) {
                printk(TBOOT_WARN"TPM: EventSequenceComplete return value = %08X\n", ret);
                ti->error = ret;
                return false;
            }
        }
    }

    hl->count = complete_out.results.count;
    for ( j=0; j<hl->count; j++ ) {
        hl->entries[j].alg = complete_out.results.digests[j].hash_alg;
        memcpy(&hl->entries[j].hash, &complete_out.results.digests[j].digest,
                sizeof(hl->entries[j].hash));
    }

    return true;
}

static bool tpm20_nv_read(struct tpm_if *ti, uint32_t locality,
                          uint32_t index, uint32_t offset,
                          uint8_t *data, uint32_t *data_size)
{
    tpm_nv_read_in read_in;
    tpm_nv_read_out read_out;
    u32 ret;

    if ( ti == NULL || data_size == NULL || *data_size == 0 )
        return false;

    if ( *data_size > MAX_NV_INDEX_SIZE )
        *data_size = MAX_NV_INDEX_SIZE;

    read_in.handle = index;
    read_in.index = index;
    read_in.sessions.num_sessions = 1;
    read_in.sessions.sessions[0] = pw_session;
    read_in.offset = offset;
    read_in.size = *data_size;

    ret = _tpm20_nv_read(locality, &read_in, &read_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: read NV index %08x from offset %08x, return value = %08X\n",
                index, offset, ret);
        ti->error = ret;
        return false;
    }

    *data_size = read_out.data.t.size;
    if( *data_size > 0 )
        memcpy(data, &read_out.data.t.buffer[0], *data_size);

    return true;
}

static bool tpm20_nv_write(struct tpm_if *ti, uint32_t locality,
                           uint32_t index, uint32_t offset,
                           const uint8_t *data, uint32_t data_size)
{
    tpm_nv_write_in write_in;
    tpm_nv_write_out write_out;
    u32 ret;

    if ( ti == NULL || data == NULL || data_size == 0 
            || data_size > MAX_NV_INDEX_SIZE )
        return false;

    write_in.handle = index;
    write_in.index = index;
    write_in.sessions.num_sessions = 1;
    write_in.sessions.sessions[0] = pw_session;
    write_in.offset = offset;
    write_in.data.t.size = data_size;
    memcpy(&write_in.data.t.buffer[0], data, data_size);

    ret = _tpm20_nv_write(locality, &write_in, &write_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: write NV %08x, offset %08x, %08x bytes, return value = %08X\n",
                index, offset, data_size, ret);
        ti->error = ret;
        return false;
    }

    return true;
}

static bool tpm20_get_nvindex_size(struct tpm_if *ti, uint32_t locality,
                                   uint32_t index, uint32_t *size)
{
    tpm_nv_read_public_in public_in;
    tpm_nv_read_public_out public_out;
    u32 ret;

    if ( ti == NULL || size == NULL )
        return false;

    public_in.index = index;

    ret = _tpm20_nv_read_public(locality, &public_in, &public_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: fail to get public data of 0x%08X in TPM NV\n", index);
        ti->error = ret;
        return false;
    }

    if (index != public_out.nv_public.t.nv_public.index) {
        printk(TBOOT_WARN"TPM: Index 0x%08X is not the one expected 0x%08X\n",
                index, index);
        ti->error = TPM_RC_FAILURE;
        return false;
    }

    *size = public_out.nv_public.t.nv_public.data_size;

    return true;
}

static bool tpm20_get_nvindex_permission(struct tpm_if *ti, uint32_t locality,
                                    uint32_t index, uint32_t *attribute)
{
    if ( ti == NULL || locality >= TPM_NR_LOCALITIES
         || index == 0 || attribute == NULL )
        return false;

    return true;
}

static bool tpm20_seal(struct tpm_if *ti, uint32_t locality,
                       uint32_t in_data_size, const uint8_t *in_data,
                       uint32_t *sealed_data_size, uint8_t *sealed_data)
{
    tpm_create_in create_in; 
    tpm_create_out create_out; 
    u32 ret;

    create_in.parent_handle = handle2048;
    create_in.sessions.num_sessions = 1;
    create_in.sessions.sessions[0] = pw_session;
    create_in.sessions.sessions[0].hmac.t.size = 2;
    create_in.sessions.sessions[0].hmac.t.buffer[0] = 0x00;
    create_in.sessions.sessions[0].hmac.t.buffer[1] = 0xff;

    create_in.public.t.public_area.type = TPM_ALG_KEYEDHASH;
    create_in.public.t.public_area.name_alg = ti->cur_alg;
    create_in.public.t.public_area.auth_policy.t.size = 0;
    *(u32 *)&create_in.public.t.public_area.object_attr = 0;
    create_in.public.t.public_area.object_attr.userWithAuth = 1;
    create_in.public.t.public_area.object_attr.noDA = 1;
    create_in.public.t.public_area.param.keyed_hash.scheme.scheme = TPM_ALG_NULL;
    create_in.public.t.public_area.unique.keyed_hash.t.size = 0;

    create_in.sensitive.t.sensitive.user_auth.t.size = sizeof(auth_str) - 1;
    memcpy(&(create_in.sensitive.t.sensitive.user_auth.t.buffer[0]),
            auth_str, sizeof(auth_str)-1);
    create_in.sensitive.t.sensitive.data.t.size = in_data_size;
    memcpy(&(create_in.sensitive.t.sensitive.data.t.buffer[0]),
            in_data, in_data_size); 

    create_in.outside_info.t.size = 0;
    create_in.creation_pcr.count = 0;

    ret = _tpm20_create(locality, &create_in, &create_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: Create return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }
    *sealed_data_size = sizeof(create_out);
    memcpy(sealed_data, &create_out, *sealed_data_size); 

    return true;
}

static bool tpm20_unseal(struct tpm_if *ti, uint32_t locality,
                         uint32_t sealed_data_size, const uint8_t *sealed_data,
                         uint32_t *secret_size, uint8_t *secret)
{
    tpm_load_in load_in; 
    tpm_load_out load_out; 
    tpm_unseal_in unseal_in; 
    tpm_unseal_out unseal_out; 
    u32 ret;

    if ( ti == NULL || locality >= TPM_NR_LOCALITIES
         || sealed_data_size == 0 || sealed_data == NULL )
        return false;

    /* For TPM 2.0, the object will need to be loaded before it may be used.*/
    load_in.parent_handle = handle2048;
    load_in.sessions.num_sessions = 1;
    load_in.sessions.sessions[0] = pw_session;
    load_in.sessions.sessions[0].hmac.t.size = 2;
    load_in.sessions.sessions[0].hmac.t.buffer[0] = 0x00;
    load_in.sessions.sessions[0].hmac.t.buffer[1] = 0xff;
    load_in.private = ((tpm_create_out *)sealed_data)->private;
    load_in.public = ((tpm_create_out *)sealed_data)->public;

    ret = _tpm20_load(locality, &load_in, &load_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: Load return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }

    unseal_in.sessions.num_sessions = 1;
    unseal_in.sessions.sessions[0] = pw_session;
    unseal_in.sessions.sessions[0].hmac.t.size = sizeof(auth_str) - 1;
    memcpy(&(unseal_in.sessions.sessions[0].hmac.t.buffer[0]),
            auth_str, sizeof(auth_str)-1);
    unseal_in.item_handle = load_out.obj_handle;

    ret = _tpm20_unseal(locality, &unseal_in, &unseal_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: Unseal return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }

    *secret_size = unseal_out.data.t.size;
    memcpy(secret, &(unseal_out.data.t.buffer[0]), *secret_size);

    return true;
}

static bool tpm20_verify_creation(struct tpm_if *ti, uint32_t sealed_data_size, uint8_t *sealed_data)
{
    if ( ti == NULL || sealed_data_size == 0 || sealed_data == NULL )
        return false;

    return true;
}

static bool tpm20_get_random(struct tpm_if *ti, uint32_t locality,
                             uint8_t *random_data, uint32_t *data_size)
{
    tpm_get_random_in random_in; 
    tpm_get_random_out random_out; 
    u32 ret, out_size, requested_size;
    static bool first_attempt;

    if ( random_data == NULL || data_size == NULL || *data_size == 0 )
        return false;

    first_attempt = true;
    requested_size = *data_size;

    random_in.bytes_req = *data_size;

    ret = _tpm20_get_random(locality, &random_in, &random_out);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: get random 0x%x bytes, return value = %08X\n", *data_size, ret);
        ti->error = ret;
        return false;
    }

    out_size = random_out.random_bytes.t.size;
    if (out_size > 0)
        memcpy(random_data, &(random_out.random_bytes.t.buffer[0]), out_size);
    *data_size = out_size;

    /* if TPM doesn't return all requested random bytes, try one more time */
    if ( out_size < requested_size ) {
        printk(TBOOT_WARN"requested 0x%x random bytes but only got 0x%x\n", requested_size, out_size);
        /* we're only going to try twice */
        if ( first_attempt ) {
            first_attempt = false;
            uint32_t second_size = requested_size - out_size;
            printk(TBOOT_WARN"trying one more time to get remaining 0x%x bytes\n", second_size);
            random_in.bytes_req = second_size;

            ret = _tpm20_get_random(locality, &random_in, &random_out);
            if ( ret != TPM_RC_SUCCESS ) {
                printk(TBOOT_WARN"TPM: get random 0x%x bytes, return value = %08X\n",
                        *data_size, ret);
                ti->error = ret;
                return false;
            }

            out_size = random_out.random_bytes.t.size;
            if (out_size > 0)
                memcpy(random_data+*data_size, 
                        &(random_out.random_bytes.t.buffer[0]), out_size);
            *data_size += out_size;
        }
    }

    return true;
}

static uint32_t tpm20_save_state(struct tpm_if *ti, uint32_t locality)
{
    u32 ret;

    if ( ti == NULL )
        return false;

    ret = _tpm20_shutdown(locality, TPM_SU_STATE);
    if ( ret != TPM_RC_SUCCESS ) {
        printk(TBOOT_WARN"TPM: Shutdown, return value = %08X\n", ret);
        ti->error = ret;
    }

    return ret;
}

#define TPM_NR_PCRS             24
#define TPM_PCR_RESETABLE_MIN   16
static bool tpm20_cap_pcrs(struct tpm_if *ti, u32 locality, int pcr)
{
    bool was_capped[TPM_NR_PCRS] = {false};
    hash_list_t cap_val;   /* use whatever val is on stack */

    if ( ti == NULL || locality >= TPM_NR_LOCALITIES || pcr == 0 )
        return false;

    cap_val.count = ti->banks;
    for (unsigned int i=0; i<ti->banks; i++)
        cap_val.entries[i].alg = ti->algs_banks[i];

    if (pcr >= 0) {
        tpm20_pcr_extend(ti, locality, pcr, &cap_val);
        return true;
    }

    /* ensure PCRs 17 + 18 are always capped */
    tpm20_pcr_extend(ti, locality, 17, &cap_val);
    tpm20_pcr_extend(ti, locality, 18, &cap_val);
    was_capped[17] = was_capped[18] = true;

    /* also cap every dynamic PCR we extended (only once) */
    /* don't cap static PCRs since then they would be wrong after S3 resume */
    memset(&was_capped, true, TPM_PCR_RESETABLE_MIN*sizeof(bool));
    for ( int i = 0; i < g_pre_k_s3_state.num_vl_entries; i++ ) {
        if ( !was_capped[g_pre_k_s3_state.vl_entries[i].pcr] ) {
            tpm20_pcr_extend(ti, locality, g_pre_k_s3_state.vl_entries[i].pcr, &cap_val);
            was_capped[g_pre_k_s3_state.vl_entries[i].pcr] = true;
        }
    }

    printk(TBOOT_INFO"cap'ed dynamic PCRs\n");
    return true;
}

static bool alg_is_supported(u16 alg)
{
    for (int i=0; i<2; i++) {
        if (alg == tboot_alg_list[i])
            return true;
    }

    return false;
}

static bool tpm20_init(struct tpm_if *ti)
{
    u32 ret;
    unsigned int i;
    tpm_info_list_t *info_list = get_tpm_info_list(g_sinit);
 
   if ( ti == NULL )
        return false;
   
   if (!txt_is_launched())
                ti->cur_loc = 0;
     else
                ti->cur_loc = 2;
   

    /* init version */
    ti->major = TPM20_VER_MAJOR;
    ti->minor = TPM20_VER_MINOR;

    /* init timeouts value */
    ti->timeout.timeout_a = TIMEOUT_A;
    ti->timeout.timeout_b = TIMEOUT_B;
    ti->timeout.timeout_c = TIMEOUT_C;
    ti->timeout.timeout_d = TIMEOUT_D;

    /* get pcr extend policy from cmdline */
    get_tboot_extpol();
    if (info_list->capabilities.tpm_nv_index_set == 0){
        /* init NV index */
        ti->tb_policy_index = 0x1200001;
        ti->lcp_own_index = 0x1400001;
        ti->tb_err_index = 0x1200002;
        ti->sgx_svn_index = 0x01800004;
    }
    else {
        ti->tb_policy_index = 0x01c10131;
        ti->lcp_own_index = 0x01c10106;
        ti->tb_err_index = 0x01c10132;
        ti->sgx_svn_index = 0x01c10104;
    }
    /* create one common password sesson*/
    create_pw_session(&pw_session);

    /* init supported alg list for banks */
    tpm_pcr_event_in event_in;
    tpm_pcr_event_out event_out;
    event_in.pcr_handle = 16;
    event_in.sessions.num_sessions = 1;
    event_in.sessions.sessions[0] = pw_session;
    event_in.data.t.size = 4;
    event_in.data.t.buffer[0] = 0;
    event_in.data.t.buffer[1] = 0xff;
    event_in.data.t.buffer[2] = 0x55;
    event_in.data.t.buffer[3] = 0xaa;
    ret = _tpm20_pcr_event(ti->cur_loc, &event_in, &event_out);
    if (ret != TPM_RC_SUCCESS) {
        printk(TBOOT_WARN"TPM: PcrEvent not successful, return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }
    ti->banks = event_out.digests.count;
    printk(TBOOT_INFO"TPM: supported bank count = %d\n", ti->banks);
    for (i=0; i<ti->banks; i++) {
        ti->algs_banks[i] = event_out.digests.digests[i].hash_alg;;
        printk(TBOOT_INFO"TPM: bank alg = %08x\n", ti->algs_banks[i]);
    }

    /* init supported alg list */
    ti->alg_count = 0;
    for (i=0; i<ti->banks; i++) {
        if (alg_is_supported(ti->algs_banks[i])) {
            ti->algs[ti->alg_count] = ti->algs_banks[i];
            ti->alg_count++;
        }
    }
    printk(TBOOT_INFO"TPM: supported alg count = %08X\n", ti->alg_count);
    for (unsigned int i=0; i<ti->alg_count; i++)
        printk(TBOOT_INFO"\t\t %08X\n", ti->algs[i]);

    if (handle2048 != 0)
        goto out;

    /* create primary object as parent obj for seal */
    tpm_create_primary_in primary_in;
    tpm_create_primary_out primary_out;
    primary_in.primary_handle = TPM_RH_NULL;
    primary_in.sessions.num_sessions = 1;
    primary_in.sessions.sessions[0].session_handle = TPM_RS_PW;
    primary_in.sessions.sessions[0].nonce.t.size = 0;
    primary_in.sessions.sessions[0].hmac.t.size = 0;
    *((u8 *)((void *)&primary_in.sessions.sessions[0].session_attr)) = 0;

    primary_in.sensitive.t.sensitive.user_auth.t.size = 2;
    primary_in.sensitive.t.sensitive.user_auth.t.buffer[0] = 0x00;
    primary_in.sensitive.t.sensitive.user_auth.t.buffer[1] = 0xff;
    primary_in.sensitive.t.sensitive.data.t.size = 0;

    primary_in.public.t.public_area.type = TPM_ALG_RSA;
    primary_in.public.t.public_area.name_alg = ti->cur_alg;
    *(u32 *)&primary_in.public.t.public_area.object_attr = 0;
    primary_in.public.t.public_area.object_attr.restricted = 1;
    primary_in.public.t.public_area.object_attr.userWithAuth = 1;
    primary_in.public.t.public_area.object_attr.decrypt = 1;
    primary_in.public.t.public_area.object_attr.fixedTPM = 1;
    primary_in.public.t.public_area.object_attr.fixedParent = 1;
    primary_in.public.t.public_area.object_attr.noDA = 1;
    primary_in.public.t.public_area.object_attr.sensitiveDataOrigin = 1;
    primary_in.public.t.public_area.auth_policy.t.size = 0;
    primary_in.public.t.public_area.param.rsa.symmetric.alg = TPM_ALG_AES;
    primary_in.public.t.public_area.param.rsa.symmetric.key_bits.aes= 128;
    primary_in.public.t.public_area.param.rsa.symmetric.mode.aes = TPM_ALG_CFB;
    primary_in.public.t.public_area.param.rsa.scheme.scheme = TPM_ALG_NULL;
    primary_in.public.t.public_area.param.rsa.key_bits = 2048;
    primary_in.public.t.public_area.param.rsa.exponent = 0;
    primary_in.public.t.public_area.unique.keyed_hash.t.size = 0;
    primary_in.outside_info.t.size = 0;
    primary_in.creation_pcr.count = 0;
    
    printk(TBOOT_DETA"TPM:CreatePrimary creating hierarchy handle = %08X\n", primary_in.primary_handle);
    ret = _tpm20_create_primary(ti->cur_loc, &primary_in, &primary_out);
    if (ret != TPM_RC_SUCCESS) {
        printk(TBOOT_WARN"TPM: CreatePrimary return value = %08X\n", ret);
        ti->error = ret;
        return false;
    }
    handle2048 = primary_out.obj_handle;
 
    printk(TBOOT_DETA"TPM:CreatePrimary created object handle = %08X\n", handle2048);
out:
    tpm_print(ti);
    return true;
}

struct tpm_if tpm_20_if = {
    .init = tpm20_init,
    .pcr_read = tpm20_pcr_read,
    .pcr_extend = tpm20_pcr_extend,
    .hash = tpm20_hash,
    .pcr_reset = tpm20_pcr_reset,
    .nv_read = tpm20_nv_read,
    .nv_write = tpm20_nv_write,
    .get_nvindex_size = tpm20_get_nvindex_size,
    .get_nvindex_permission = tpm20_get_nvindex_permission,
    .seal = tpm20_seal,
    .unseal = tpm20_unseal,
    .verify_creation = tpm20_verify_creation,
    .get_random = tpm20_get_random,
    .save_state = tpm20_save_state,
    .cap_pcrs = tpm20_cap_pcrs,
};


/*
 * Local variables:
 * mode: C
 * c-set-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
