/* caam_driver.c
 *
 * Copyright (C) 2006-2020 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

#if defined(__INTEGRITY) || defined(INTEGRITY)

/* build into Integrity kernel */
#include <bsp.h>
#include "wolfssl/wolfcrypt/port/caam/caam_driver.h"

#define CAAM_READ(reg) *(volatile unsigned int*)(reg)
#define CAAM_WRITE(reg, in) *(volatile unsigned int*)(reg) = (in);

#define DESC_COUNT 1
#define MAX_BUF 20
#define BUFFER_COUNT (MAX_BUF * DESC_COUNT)

/* CAAM descriptors can only be 64 unsigned ints */
#define MAX_DESC_SZ 64

/* 64 byte buffer for when data crosses a page boundary */
#define ALIGN_BUF 16

/* MAX_CTX is 64 bytes (sha512 digest) + 8 bytes (CAAM length value) */
#define MAX_CTX 18

#define MIN_READ_REG 0xF2100000
#define MAX_READ_REG 0XF2110000

struct JobRing {
    Address JobIn;
    Address JobOut;
    Address Desc;
    Value   page;    /* page allocation for descriptor to use */
};

struct buffer {
    Address data;
    Address dataSz;
};

/* CAAM descriptor */
struct DescStruct {
    struct IORequestStruct TheIORequest;
    struct CAAM_DEVICE*    caam;
    struct buffer          buf[MAX_BUF]; /* buffers holding data input address */
    UINT4                  desc[MAX_DESC_SZ]; /* max size of 64 word32 */
    UINT4                  aadSzBuf[4];       /* Formatted AAD size for CCM */
    UINT4                  alignBuf[ALIGN_BUF]; /* 64 byte buffer for non page
                                                   align */
    UINT4                  iv[MAX_CTX]; /* AES IV and also hash state */
    UINT4                  ctxBuf[MAX_CTX]; /* key */
    Address                output; /* address to output buffer */
    Address                ctxOut; /* address to update buffer holding state */
    Value                  alignIdx;/* index for align buffer */
    Value                  idx;     /* index for descriptor buffer */
    Value                  headIdx; /* for first portion of descriptor buffer */
    Value                  lastIdx; /* for last portion of descriptor buffer */
    Value                  outputIdx; /* idx to output buffer in "buf" */
    Value                  inputSz;   /* size of input buffer */
    Value                  ctxSz;     /* size of CTX/Key buffer */
    Value                  aadSz;     /* AAD size for CCM */
    Value                  lastFifo;
    Value                  type;
    Value                  state;
    Value                  DescriptorCount;
    Boolean                running; /* True if building/running descriptor is
                                       in process */
};

struct CAAM_DEVICE {
    struct IODeviceVectorStruct caamVector;
    struct IODescriptorStruct   IODescriptorArray[BUFFER_COUNT];
    struct DescStruct           DescArray[DESC_COUNT];
    volatile Value              InterruptStatus;
    CALL                        HandleInterruptCall;
    struct JobRing              ring;
};

#define DRIVER_NAME "wolfSSL_CAAM_Driver"

static struct CAAM_DEVICE caam;

/******************************************************************************
  Internal CAAM Job Ring and partition functions
  ****************************************************************************/

/* flush job ring and reset */
static Error caamReset(void)
{
    int t = 100000; /* time out counter for flushing job ring */

    /* make sure interrupts are masked in JRCFGR0_LS register */
    CAAM_WRITE(CAAM_BASE | 0x1054, CAAM_READ(CAAM_BASE | 0x1054) | 1);

    /* flush and reset job rings using JRCR0 register */
    CAAM_WRITE(CAAM_BASE | 0x106C, 1);

    /* check register JRINTR for if halt is in progress */
    while (t > 0 && ((CAAM_READ(CAAM_BASE | 0x104C) & 0x4) == 0x4)) t--;
    if (t == 0) {
        /*unrecoverable failure, the job ring is locked, up hard reset needed*/
        return NotRestartable;
    }

    /* now that flush has been done restart the job ring */
    t = 100000;
    CAAM_WRITE(CAAM_BASE | 0x106C, 1);
    while (t > 0 && ((CAAM_READ(CAAM_BASE | 0x106C) & 1) == 1)) t--;
    if (t == 0) {
        /*unrecoverable failure, reset bit did not return to 0 */
        return NotRestartable;
    }

    /* reset most registers and state machines in CAAM using MCFGR register
       also reset DMA */
    CAAM_WRITE(CAAM_BASE | 0x0004, 0x90000000);

    return Success;
}

/* returns MemoryMapMayNotBeEmpty if page/par is already owned
 * returns Success on success
 * all other returns is an error state
 */
static Error caamCreatePartition(unsigned char page, unsigned char par)
{
    /* check ownership of partition */
    if ((CAAM_READ(CAAM_BASE | 0x1FBC) & (0x3 << (par * 2))) > 0) {
        return MemoryMapMayNotBeEmpty;
    }

    /* set generic all access permissions, gets reset later */
    CAAM_WRITE(CAAM_BASE | (0x1108 + (par * 16)), 0xF);
    CAAM_WRITE(CAAM_BASE | (0x110C + (par * 16)), 0xF);
    CAAM_WRITE(CAAM_BASE | (0x1104 + (par * 16)), 0xFF);

    /* check ownership of page */
    CAAM_WRITE(CAAM_BASE | 0x10F4, (page << 16) | 0x5);
    /* wait for inquiry cmd to complete */
    while ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x0000C000) > 0 &&
       (CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000)  == 0) {
    }
    if ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x000000C0) == 0xC0) {
        /* owns the page can dealloc it */
        CAAM_WRITE(CAAM_BASE | 0x10F4, (page << 16) | 0x2);
        while ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x0000C000) > 0 &&
               (CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000)  == 0) {}
        if ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000)  > 0) {
            /* error while deallocating page */
            return MemoryMapMayNotBeEmpty; /* PSP set on page or is unavailable */
        }
    }
    else {
        /* check if owned by someone else */
        if ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x000000C0) != 0) {
            return MemoryMapMayNotBeEmpty;
        }
    }

    /* allocate page to partition */
    CAAM_WRITE(CAAM_BASE | 0x10F4, (page << 16) | (par << 8) | 0x1);
    /* wait for alloc cmd to complete */
    while ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x0000C000) > 0 &&
       (CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000)  == 0) {
    }

    if ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000) > 0) {
        return MemoryOperationNotPerformed;
    }

    /* double check ownership now of page */
    CAAM_WRITE(CAAM_BASE | 0x10F4, (page << 16) | 0x5);
    /* wait for inquiry cmd to complete */
    while ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x0000C000) > 0 &&
       (CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000)  == 0) {
    }
    if ((CAAM_READ(CAAM_BASE | 0x10FC) & 0x0000000F) == 0 ||
        (CAAM_READ(CAAM_BASE | 0x10FC) & 0x00003000) > 0) {
        /* page not owned */
        return MemoryOperationNotPerformed;
    }

    return Success;
}


/* Gets the status of a job. Returns Waiting if no output jobs ready to be
 * read.
 * If no jobs are done then return Waiting
 * If jobs are done but does not match desc then return NoActivityReady
 * Status holds the error values if any */
static Error caamGetJob(struct CAAM_DEVICE* dev, UINT4* status)
{
    UINT4 reg = CAAM_READ(CAAM_BASE | 0x1044); /* JRSTAR0 status */
    if (status) {
        *status = 0;
    }

    /* check for DECO, CCB, and Job Ring error state JRSTAR0 register */
    if (((reg & 0xF0000000) == 0x20000000) ||         /* CCB error */
        ((reg & 0xF0000000) == 0x40000000)||  /* DECO error */
        ((reg & 0xF0000000) == 0x60000000)) { /* Job Ring error */

        if ((reg & 0x0000000F) > 0) {
            *status = reg;
            return Failure;
        }
    }

    /* Check number of done jobs in output list */
    reg = CAAM_READ(CAAM_BASE | 0x103C);
    if ((reg & 0x000003FF) > 0) {
        UINT4* out = (UINT4*)(dev->ring.JobOut);
        if (status) {
            *status = out[1];
        }

        if ((dev->ring.Desc ^ 0xF0000000) != out[0]) {
            db_printf("CAAM job completed vs expected mismatch");
            return NoActivityReady;
        }

        if (out[1] > 0) {
            return Failure;
        }

        /* increment jobs removed */
        CAAM_WRITE(CAAM_BASE | 0x1034, 1);
    }
    else {
        /* check if the CAAM is idle and not processing any descriptors */
        if ((CAAM_READ(CAAM_BASE | 0x0FD4) & 0x00000002) == 2 /* idle */
        && (CAAM_READ(CAAM_BASE | 0x0FD4) & 0x00000001) == 0) {
            return NoActivityReady;
        }

        return Waiting;
    }

    return Success;
}


/* Initialize CAAM RNG
 * returns 0 on success */
static int caamInitRng(struct CAAM_DEVICE* dev)
{
    UINT4 reg, status;
    int ret = 0;

    /* Set up use of the TRNG for seeding wolfSSL HASH-DRBG */
    CAAM_WRITE(CAAM_RTMCTL, CAAM_PRGM);
    CAAM_WRITE(CAAM_RTMCTL, CAAM_READ(CAAM_RTMCTL) | 0x40); /* reset */

    /* Set up reading from TRNG */
    CAAM_WRITE(CAAM_RTMCTL, CAAM_READ(CAAM_RTMCTL) | CAAM_TRNG);

    /* Set up delay for TRNG @TODO Optimizations?
     * Shift left with RTSDCTL because 0-15 is for sample number
     * Also setting the max and min frequencies */
    CAAM_WRITE(CAAM_RTSDCTL, (CAAM_ENT_DLY << 16) | 0x09C4);
    CAAM_WRITE(CAAM_RTFRQMIN, CAAM_ENT_DLY >> 1); /* 1/2      */
    CAAM_WRITE(CAAM_RTFRQMAX, CAAM_ENT_DLY << 3); /* up to 8x */

    /* Set back to run mode and clear RTMCL error bit */
    reg = CAAM_READ(CAAM_RTMCTL) ^ CAAM_PRGM;

    CAAM_WRITE(CAAM_RTMCTL, reg);
    reg = CAAM_READ(CAAM_RTMCTL);
    reg |= CAAM_CTLERR;
    CAAM_WRITE(CAAM_RTMCTL, reg);

    /* check input slot is available and then add */
    if (CAAM_READ(CAAM_BASE | 0x1014) > 0) {
        UINT4* in = (UINT4*)dev->ring.JobIn;

        memcpy((unsigned char*)dev->ring.Desc, (unsigned char*)wc_rng_start,
        sizeof(wc_rng_start));

        in[0] = dev->ring.Desc ^ 0xF0000000; /* physical address */
        CAAM_WRITE(CAAM_IRJAR0, 0x00000001);
    }
    else {
        return Waiting;
    }

    do {
        ret = caamGetJob(dev, &status);
        /* @TODO use a better way to chill out CPU. */
    } while (ret == Waiting);

    return ret;
}


static Error caamDoJob(struct DescStruct* desc)
{
    Error ret;
    UINT4 status;

    /* clear and set desc size */
    desc->desc[0] &= 0xFFFFFF80;
    desc->desc[0] += desc->idx;

    /* check input slot is available and then add */
    if (CAAM_READ(CAAM_BASE | 0x1014) > 0) {
        UINT4* in = (UINT4*)desc->caam->ring.JobIn;

        memcpy((unsigned char*)desc->caam->ring.Desc, (unsigned char*)desc->desc,
        (desc->idx + 1) * sizeof(UINT4));

        in[0] = desc->caam->ring.Desc ^ 0xF0000000; /* physical address */
        CAAM_WRITE(CAAM_IRJAR0, 0x00000001);
    }
    else {
        return Waiting;
    }

    do {
        ret = caamGetJob(desc->caam, &status);
    /* @TODO use a better way to chill out CPU. */
    } while (ret == Waiting);

    if (status != 0 || ret != Success) {
        #if 0
        /* Used during testing to print out descriptor */
        {
        char msg[2048];
        char* pt = msg;
        int z;

        memset(msg, 0, sizeof(msg));
        for (z = 0; z < desc->idx; z++) {
                    snprintf(pt, sizeof(msg) - (z * 21), "desc[%d] = 0x%8.8x, ",
                z, desc->desc[z]);
            pt += 21;
        }
        snprintf(pt, sizeof(msg) - (z * 21), "status = 0x%8.8x\n", status);
        if (desc->buf[0].data != 0) { /* for testing */
            memcpy((char*)desc->buf[0].data, msg, sizeof(msg));
        }
        }
        #endif


        /* try to reset after error */
        caamReset();
        return ret;
    }

    return Success;
}


/* handle input or output buffers
 * NOTES: if sz == 0 then read all the rest of the buffers available
 * when align == 1 then there is no alignment constraints
 *
 * returns the data size in bytes on success. With failure a negative value is
 * returned.
 */
static int caamAddIO(struct DescStruct* desc, UINT4 options, UINT4 sz,
    UINT4 align, UINT4* idx)
{
    int i, outSz = 0;

    if (align == 0) {
        return -1; /* programming error */
    }

    for (i = *idx; i < desc->DescriptorCount; i++) {
        /* input must be a multiple of "align" bytes */
        struct buffer* buf = &desc->buf[i];
        int blocks = buf->dataSz / align;
        Address data   = buf->data;
        Address dataSz = buf->dataSz;

        if (outSz >= sz && sz != 0) {
            break;
        }

        if (dataSz % align > 0) {
            /* store potential overlap */
            int tmpSz  = dataSz % align;
            int add = (tmpSz < (align - desc->alignIdx)) ? tmpSz :
                align - desc->alignIdx;
            unsigned char* local = (unsigned char*)desc->alignBuf;

            /* if already something in the buffer then add from front */
            if (desc->alignIdx > 0) {
                memcpy((unsigned char*)&local[desc->alignIdx],
                (unsigned char*)data, add);
                data += add;
            }
            else {
                memcpy((unsigned char*)&local[desc->alignIdx],
                (unsigned char*)data + (blocks * align), add);
            }
            dataSz -= add;
            desc->alignIdx += add;
        }

        if (desc->alignIdx == align) {
            desc->lastFifo = desc->idx;
            if (desc->idx + 2 > MAX_DESC_SZ) {
                return -1;
            }
            desc->desc[desc->idx++] = options + desc->alignIdx;
            desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->alignBuf);
            ASP_FlushCaches((Address)desc->alignBuf, desc->alignIdx);
            outSz += desc->alignIdx;
        }

        if (blocks > 0) {
            desc->lastFifo = desc->idx;
            if (desc->idx + 2 > MAX_DESC_SZ) {
                return -1;
            }
            desc->desc[desc->idx++] = options + (blocks * align);
            desc->desc[desc->idx++] = BSP_VirtualToPhysical(data);
            outSz += (blocks * align);

            /* only one buffer available for align cases so exit here and make
            a new descriptor after running current one */
            if (desc->alignIdx == align) {
                desc->alignIdx = 0;
                i++; /* start at next buffer */
                break;
            }
        }
    }

    *idx = i;
    return outSz;
}


/******************************************************************************
  IODevice Register Read and Write
  ****************************************************************************/

static Error caamReadRegister(IODeviceVector ioCaam, Value reg, Value *out)
{
    if (reg < MIN_READ_REG || reg > MAX_READ_REG) {
         return IllegalRegisterNumber;
    }

    switch (reg) {
    case CAAM_STATUS:
    case CAAM_VERSION_MS:
    case CAAM_VERSION_LS:
    case CAMM_SUPPORT_MS:
    case CAMM_SUPPORT_LS:
    case CAAM_RTMCTL:
        *out = CAAM_READ(reg);
        break;

    default:
        return IllegalRegisterNumber;
    }

    (void)ioCaam;
    return Success;
}


static Error caamWriteRegister(IODeviceVector ioCaam, Value reg, Value in)
{
    /* Should be no need for writes */
    return OperationNotAllowedOnTheUniversalIODevice;
}


/******************************************************************************
  CAAM Blob Operations
  ****************************************************************************/

/* limit on size due to size of job ring being 64 word32's */
static Error caamBlob(struct DescStruct* desc)
{
    Error err;
    UINT4 keyType = 0x00000C08; /* default red */
    UINT4 i = 0;
    int sz = 0, ret;

    if (desc->idx + 3 > MAX_DESC_SZ) {
        return Failure;
    }

    /*default to Red Key type, with offset of 12 and 8 byte load to context 2*/
    desc->desc[desc->idx++] = (CAAM_LOAD_CTX | CAAM_CLASS2 | CAAM_IMM | keyType);

    /* add key modifier */
    if (i < desc->DescriptorCount) {
        UINT4* pt;
        Address data   = desc->buf[i].data;
        Address dataSz = desc->buf[i].dataSz;

        pt = (UINT4*)data;
        if (dataSz < 8) { /* expecting 8 bytes for key modifier*/
            return TooManyBuffers;
        }
        desc->desc[desc->idx++] = pt[0];
        desc->desc[desc->idx++] = pt[1];
    }

    /* add input */
    while (sz < desc->inputSz && i < desc->DescriptorCount) {
        ret = caamAddIO(desc, CAAM_SEQI, desc->inputSz - sz, 1, &i);
        if (ret < 0) { /* handle error case */
            return TooManyBuffers;
        }
        sz += ret;
    }
    desc->outputIdx = i;

    /* add output */
    if (caamAddIO(desc, CAAM_SEQO, 0, 1, &i) < 0) {
        return TooManyBuffers;
    }

    if (desc->idx + 1 > MAX_DESC_SZ) {
        return Failure;
    }
    desc->desc[desc->idx++] = CAAM_OP |  CAAM_OPID_BLOB | desc->type;

    if ((err = caamDoJob(desc)) != Success) {
        return err;
    }

    /* flush output buffers */
    for (i = desc->outputIdx; i < desc->DescriptorCount; i++) {
        ASP_FlushCaches(desc->buf[i].data, desc->buf[i].dataSz);
    }

    return Success;
}


/******************************************************************************
  CAAM AES Operations
  ****************************************************************************/

/* returns amount written on success and negative value in error case.
 * Is different from caamAddIO in that it only adds a single input buffer
 * rather than multiple ones.
 */
static int caamAesInput(struct DescStruct* desc, UINT4* idx, int align,
    UINT4 totalSz)
{
    int sz;
    UINT4 i = *idx;

    /* handle alignment constraints on input */
    if (desc->alignIdx > 0) {
        sz = desc->alignIdx;

        /* if there is more input buffers then add part of it */
        if (i < desc->outputIdx && i < desc->DescriptorCount) {
            sz = align - desc->alignIdx;
            sz = (sz <= desc->buf[i].dataSz) ? sz : desc->buf[i].dataSz;
            memcpy((unsigned char*)(desc->alignBuf) + desc->alignIdx,
                   (unsigned char*)(desc->buf[i].data), sz);

            desc->buf[i].dataSz -= sz;
            desc->buf[i].data   += sz;
            sz += desc->alignIdx;
        }

        if (desc->idx + 2 > MAX_DESC_SZ) {
            return -1;
        }
        ASP_FlushCaches((Address)desc->alignBuf, sz);
        desc->desc[desc->idx++] = (CAAM_FIFO_L | FIFOL_TYPE_LC1 |
                                   CAAM_CLASS1 | FIFOL_TYPE_MSG) + sz;
        desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->alignBuf);
        desc->alignIdx = 0;
    }
    else {
        sz = desc->buf[i].dataSz;
        if ((totalSz + sz) == desc->inputSz) { /* not an issue on final */
            align = 1;
        }

        desc->alignIdx = sz % align;
        if (desc->alignIdx != 0) {
            sz -= desc->alignIdx;
            memcpy((unsigned char*)desc->alignBuf,
                   (unsigned char*)(desc->buf[i].data) + sz,
                   desc->alignIdx);
        }

        if (desc->idx + 2 > MAX_DESC_SZ) {
            return -1;
        }
        desc->desc[desc->idx++] = (CAAM_FIFO_L | FIFOL_TYPE_LC1 |
                                   CAAM_CLASS1 | FIFOL_TYPE_MSG) + sz;
        desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->buf[i].data);
        i++;
    }

    *idx = i;
    return sz;
}


/* returns enum Success on success, all other return values should be
 * considered an error.
 *
 * ofst    is the amount of leftover buffer from previous calls
 * inputSz is the amount of input in bytes that is being matched to output
 */
static Error caamAesOutput(struct DescStruct* desc, int* ofst, UINT4 inputSz)
{
    int offset = *ofst;

    if (desc->output != 0 && offset > 0 && inputSz > 0) {
        UINT4 addSz;

        /* handle potential leftovers */
        addSz = (inputSz >= offset) ? offset : inputSz;

        inputSz -= addSz;
        desc->desc[desc->idx++] = CAAM_FIFO_S | FIFOS_TYPE_MSG + addSz;
        if (inputSz > 0) { /* check if expecting more output */
            desc->desc[desc->idx - 1] |= CAAM_FIFOS_CONT;
        }
        desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->output);

        if (addSz == offset) {
            /* reset */
            desc->output = 0;
            offset       = 0;
        }
        else {
            offset -= addSz;
            desc->output += addSz;

            if (offset < 0) {
                return TransferFailed;
            }
        }
    }

    for (; desc->lastIdx < desc->DescriptorCount; desc->lastIdx++) {
        struct buffer* buf = &desc->buf[desc->lastIdx];

        if (inputSz > 0) {
            int tmp;

            if (buf->dataSz <= inputSz) {
                tmp = buf->dataSz;
            }
            else {
                offset = buf->dataSz - inputSz;
                tmp    = inputSz;
                desc->output = buf->data + tmp;
            }
            inputSz -= tmp;
            if (desc->idx + 2 > MAX_DESC_SZ) {
                return TransferFailed;
            }
            desc->desc[desc->idx++] = CAAM_FIFO_S | FIFOS_TYPE_MSG + tmp;
            if (inputSz > 0) { /* check if expecting more output */
                desc->desc[desc->idx - 1] |= CAAM_FIFOS_CONT;
            }
            desc->desc[desc->idx++] = BSP_VirtualToPhysical(buf->data);
        }
        else {
            break;
        }
    }

    *ofst = offset;
    return Success;
}


/* check size of output and get starting buffer for it */
static Error caamAesOutSz(struct DescStruct* desc, UINT4 i)
{
    int sz = 0;

    for (desc->outputIdx = i; desc->outputIdx < desc->DescriptorCount &&
    sz < desc->inputSz; desc->outputIdx++) {
        sz += desc->buf[desc->outputIdx].dataSz;
    }
    desc->lastIdx = desc->outputIdx;

    /* make certain that output size is same as input */
    sz = 0;
    for (; desc->lastIdx < desc->DescriptorCount; desc->lastIdx++) {
        sz += desc->buf[desc->lastIdx].dataSz;
    }
    if (sz != desc->inputSz) {
        return SizeIsTooLarge;
    }
    desc->lastIdx = desc->outputIdx;

    return Success;
}


/* AES operations follow the buffer sequence of KEY -> (IV) -> Input -> Output
 */
static Error caamAes(struct DescStruct* desc)
{
    struct buffer* ctx[3];
    struct buffer* iv[3];
    Value ofst = 0;
    Error err;
    UINT4 i, totalSz = 0;
    int ctxIdx = 0;
    int ivIdx  = 0;
    int offset = 0;
    int align  = 1;
    int sz     = 0;

    int ctxSz = desc->ctxSz;

    if (desc->state != CAAM_ENC && desc->state != CAAM_DEC) {
        return IllegalStatusNumber;
    }

    if (ctxSz != 16 && ctxSz != 24 && ctxSz != 32) {
        return ArgumentError;
    }

    /* get key */
    for (i = 0; i < desc->DescriptorCount; i++) {
        struct buffer* buf = &desc->buf[i];
        unsigned char* local = (unsigned char*)desc->ctxBuf;

        if (sz < ctxSz && sz < (MAX_CTX * sizeof(UINT4))) {
            ctx[ctxIdx] = buf;
            sz += buf->dataSz;

            memcpy((unsigned char*)&local[offset],
                   (unsigned char*)ctx[ctxIdx]->data, ctx[ctxIdx]->dataSz);
            offset += ctx[ctxIdx]->dataSz;
            ctxIdx++;
        }
        else {
            break;
        }
    }

    /* sanity checks on size of key */
    if (sz > ctxSz) {
        return SizeIsTooLarge;
    }
    if (ctxSz > (MAX_CTX * sizeof(UINT4)) - 16) {
        return ArgumentError;
    }

    /* Flush cache of ctx buffer then :
       Add KEY Load command          0x0220000X
       Add address to read key from  0xXXXXXXXX */
    ASP_FlushCaches((Address)desc->ctxBuf, ctxSz);
    if (desc->idx + 2 > MAX_DESC_SZ) {
        return TransferFailed;
    }
    desc->desc[desc->idx++] = (CAAM_KEY | CAAM_CLASS1 | CAAM_NWB) + ctxSz;
    desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->ctxBuf);

    /* get IV if needed by algorithm */
    switch (desc->type) {
        case CAAM_AESECB:
            break;

        case CAAM_AESCTR:
            ofst = 0x00001000;
            /* fall through because states are the same only the offset changes */

        case CAAM_AESCBC:
        {
            int maxSz = 16; /* default to CBC/CTR max size */

            sz = 0;
            offset = 0;
            for (; i < desc->DescriptorCount; i++) {
                struct buffer* buf = &desc->buf[i];
                unsigned char* local = (unsigned char*)desc->iv;

                if (sz < maxSz) {
                    iv[ivIdx] = buf;

                    if (buf->dataSz + sz > maxSz) {
                        return SizeIsTooLarge;
                    }

                    sz += buf->dataSz;
                    memcpy((unsigned char*)&local[offset],
                        (unsigned char*)iv[ivIdx]->data, iv[ivIdx]->dataSz);
                    offset += iv[ivIdx]->dataSz;
                    ivIdx++;
                }
                else {
                    break;
                }
            }

            if (sz != maxSz) {
                /* invalid IV size */
                return SizeIsTooLarge;
            }

            ASP_FlushCaches((Address)desc->iv, maxSz);
            if (desc->idx + 2 > MAX_DESC_SZ) {
                return TransferFailed;
            }
            desc->desc[desc->idx++] = (CAAM_LOAD_CTX | CAAM_CLASS1 | ofst) + maxSz;
            desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->iv);
         }
         break;

        default:
            return OperationNotImplemented;
    }

    /* write operation */
    if (desc->idx + 1 > MAX_DESC_SZ) {
        return TransferFailed;
    }
    desc->desc[desc->idx++] = CAAM_OP | CAAM_CLASS1 | desc->type |
             CAAM_ALG_UPDATE | desc->state;

    /* find output buffers */
    if (caamAesOutSz(desc, i) != Success) {
        return SizeIsTooLarge;
    }

    /* set alignment constraints */
    if (desc->type == CAAM_AESCBC || desc->type == CAAM_AESECB) {
        align = 16;
    }

    /* indefinite loop for input/output buffers */
    desc->headIdx = desc->idx;
    desc->output  = 0;
    offset = 0; /* store left over amount for output buffer */
    do {
        desc->idx = desc->headIdx; /* reset for each loop */

        /* add a single input buffer (multiple ones was giving deco watch dog
         * time out errors on the FIFO load of 1c.
         * @TODO this could be a place for optimization if more data could be
         * loaded in at one time */
        if ((sz = caamAesInput(desc, &i, align, totalSz)) < 0) {
            return TransferFailed;
        }
        totalSz += sz;

        if (caamAesOutput(desc, &offset, sz) != Success) {
            return TransferFailed;
        }

        /* store updated IV */
        if (ivIdx > 0) {
            if (desc->idx + 2 > MAX_DESC_SZ) {
                return TransferFailed;
            }
            desc->desc[desc->idx++] = CAAM_STORE_CTX | CAAM_CLASS1 | ofst | 16;
            desc->desc[desc->idx++] = BSP_VirtualToPhysical((Address)desc->iv);
        }

        if ((err = caamDoJob(desc)) != Success) {
            return err;
        }
        ASP_FlushCaches((Address)desc->iv, 16);
    } while (desc->lastIdx < desc->DescriptorCount || offset > 0);

    /* flush output buffers */
    for (i = desc->outputIdx; i < desc->lastIdx; i++) {
        ASP_FlushCaches(desc->buf[i].data, desc->buf[i].dataSz);
    }

    /* handle case with IV */
    if (ivIdx > 0) {
        unsigned char* pt = (unsigned char*)desc->iv;
        ASP_FlushCaches((Address)pt, 16);
        for (i = 0; i < ivIdx; i++) {
            memcpy((unsigned char*)iv[i]->data, pt, iv[i]->dataSz);
            pt += iv[i]->dataSz;
            ASP_FlushCaches(iv[i]->data, iv[i]->dataSz);
        }
    }

    return Success;
}


/******************************************************************************
  CAAM AEAD Operations
  ****************************************************************************/

/* AEAD operations follow the buffer sequence of KEY -> (IV or B0 | CTR0) -> (AD)
 * -> Input -> Output
 *
 */
static Error caamAead(struct DescStruct* desc)
{
    struct buffer* ctx[3];
    struct buffer* iv[3];
    Value ofst    = 0;
    UINT4 state   = CAAM_ALG_INIT;
    UINT4 totalSz = 0;
    Error err;
    UINT4 i;
    int ctxIdx = 0;
    int ivIdx  = 0;
    int offset = 0;
    int sz     = 0;
    int ivSz   = 32; /* size of B0 | CTR0 for CCM mode */
    int ctxSz  = desc->ctxSz;
    int align  = 16; /* input should be multiples of 16 bytes unless is final */
    int opIdx;

    if (desc->state != CAAM_ENC && desc->state != CAAM_DEC) {
        return IllegalStatusNumber;
    }

    /* sanity check is valid AES key size */
    if (ctxSz != 16 && ctxSz != 24 && ctxSz != 32) {
        return ArgumentError;
    }

    /* get key */
    for (i = 0; i < desc->DescriptorCount; i++) {
        struct buffer* buf = &desc->buf[i];
        unsigned char* local = (unsigned char*)desc->ctxBuf;

        if (sz < ctxSz && sz < (MAX_CTX * sizeof(UINT4))) {
            ctx[ctxIdx] = buf;
            sz += buf->dataSz;

            memcpy((unsigned char*)&local[offset],
                   (unsigned char*)ctx[ctxIdx]->data, ctx[ctxIdx]->dataSz);
            offset += ctx[ctxIdx]->dataSz;
            ctxIdx++;
        }
        else {
            break;
        }
    }

    /* sanity checks on size of key */
    if (sz > ctxSz) {
        return SizeIsTooLarge;
    }

    /* Flush cache of ctx buffer then :
       Add KEY Load command          0x0220000X
       Add address to read key from  0xXXXXXXXX */
    ASP_FlushCaches((Address)desc->ctxBuf, ctxSz);
    if (desc->idx + 2 > MAX_DESC_SZ) {
        return TransferFailed;
    }
    desc->desc[desc->idx++] = (CAAM_KEY | CAAM_CLASS1 | CAAM_NWB) + ctxSz;
    desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->ctxBuf);

    desc->headIdx = desc->idx;
    desc->output  = 0;
    offset = 0; /* store left over amount for output buffer */
    do {
        desc->idx = desc->headIdx; /* reset for each loop */

        /* write operation */
        if (desc->idx + 1 > MAX_DESC_SZ) {
            return TransferFailed;
        }
        opIdx = desc->idx;
        desc->desc[desc->idx++] = CAAM_OP | CAAM_CLASS1 | state | desc->type |
                                  desc->state;

        /* get IV if needed by algorithm */
        switch (desc->type) {
            case CAAM_AESCCM:
                if ((state & CAAM_ALG_INIT) == CAAM_ALG_INIT) {
                    sz = 0;
                    offset = 0;
                    for (; i < desc->DescriptorCount; i++) {
                        struct buffer* buf = &desc->buf[i];
                        unsigned char* local = (unsigned char*)desc->iv;

                        if (sz < ivSz) {
                            iv[ivIdx] = buf;

                            if (buf->dataSz + sz > ivSz) {
                                return SizeIsTooLarge;
                            }

                            sz += buf->dataSz;
                            memcpy((unsigned char*)&local[offset],
                            (unsigned char*)iv[ivIdx]->data, iv[ivIdx]->dataSz);
                            offset += iv[ivIdx]->dataSz;
                            ivIdx++;
                        }
                        else {
                            break;
                        }
                    }

                    if (sz != ivSz) {
                        /* invalid IV size */
                        return SizeIsTooLarge;
                    }
                    offset = 0;
                }

                ASP_FlushCaches((Address)desc->iv, ivSz);
                if (desc->idx + 2 > MAX_DESC_SZ) {
                    return TransferFailed;
                }
                desc->desc[desc->idx++] = (CAAM_LOAD_CTX | CAAM_CLASS1 | ofst)
                                           + ivSz;
                desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->iv);
                break;

            default:
                return OperationNotImplemented;
        }


        /********* handle AAD -- is only done with Init **********************/
        if ((state & CAAM_ALG_INIT) == CAAM_ALG_INIT) {
            if ((desc->type == CAAM_AESCCM) && (desc->aadSz > 0)) {
                /* set formatted AAD buffer size for CCM */
                ASP_FlushCaches((Address)desc->aadSzBuf, sizeof(desc->aadSzBuf));
                desc->desc[desc->idx++] = CAAM_FIFO_L | CAAM_CLASS1 |
                    FIFOL_TYPE_AAD + desc->aadSz;
                desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->aadSzBuf);

                /* now set aadSz to unformatted version for getting buffers */
                if (desc->aadSz == 2) {
                    unsigned char* pt = (unsigned char*)desc->aadSzBuf;
                    desc->aadSz = (((UINT4)pt[0] & 0xFF) << 8) |
                           ((UINT4)pt[1] & 0xFF);
                }
                else {
                    unsigned char* pt = (unsigned char*)desc->aadSzBuf;
                    desc->aadSz = (((UINT4)pt[2] & 0xFF) << 24) |
                                  (((UINT4)pt[3] & 0xFF) << 16) |
                                  (((UINT4)pt[4] & 0xFF) <<  8) |
                                   ((UINT4)pt[5] & 0xFF);
                }
            }

            /* get additional data buffers */
            if (desc->aadSz > 0) {
                sz = 0;
                for (; i < desc->DescriptorCount; i++) {
                    struct buffer* buf = &desc->buf[i];
                    if (sz < desc->aadSz) {
                        if (desc->idx + 2 > MAX_DESC_SZ) {
                            return TransferFailed;
                        }
                        desc->lastFifo = desc->idx;
                        desc->desc[desc->idx++] = CAAM_FIFO_L | CAAM_CLASS1 |
                                                  FIFOL_TYPE_AAD + buf->dataSz;
                        desc->desc[desc->idx++] = BSP_VirtualToPhysical(buf->data);
                        sz += buf->dataSz;
                    }
                    else {
                        break;
                    }
                }

                /* flush AAD from FIFO and pad it to 16 byte block */
                desc->desc[desc->lastFifo] |= FIFOL_TYPE_FC1;
            }

            /* find output buffers */
            if (caamAesOutSz(desc, i) != Success) {
                return SizeIsTooLarge;
            }
        }

        /* handle alignment constraints on input */
        if ((sz = caamAesInput(desc, &i, align, totalSz)) < 0) {
            return TransferFailed;
        }
        totalSz += sz;

        /* handle output buffers  */
        if (caamAesOutput(desc, &offset, sz) != Success) {
            return TransferFailed;
        }

        /* store updated IV, if is last then set offset and final for MAC */
        if ((desc->lastIdx == desc->DescriptorCount) && (offset == 0)) {
            ivSz = 16;
            if (desc->state == CAAM_ENC) {
                ofst = 32 << 8; /* offset is in 15-8 bits */
            }
            else {
                ofst = 0;
            }
            desc->desc[opIdx] |= CAAM_ALG_FINAL;
        }
        else {
            /* if not final then store and use ctr and encrypted ctr from
                context dword 2,3 and 4,5. Also store MAC and AAD info from
                context dword 6. */
            ivSz = 56;
            ofst = 0;
        }

        if (desc->idx + 2 > MAX_DESC_SZ) {
            return TransferFailed;
        }
        desc->desc[desc->idx++] = CAAM_STORE_CTX | CAAM_CLASS1 | ofst | ivSz;
        desc->desc[desc->idx++] = BSP_VirtualToPhysical((Address)desc->iv);

        if ((err = caamDoJob(desc)) != Success) {
            return err;
        }
        state = CAAM_ALG_UPDATE;
    } while (desc->lastIdx < desc->DescriptorCount || offset > 0);

    /* flush output buffers */
    for (i = desc->outputIdx; i < desc->lastIdx; i++) {
        ASP_FlushCaches(desc->buf[i].data, desc->buf[i].dataSz);
    }

    /* handle case with IV (This is also the output of MAC with AES-CCM) */
    if (ivIdx > 0) {
        unsigned char* pt = (unsigned char*)desc->iv;
        ASP_FlushCaches((Address)pt, ivSz);
        for (i = 0; i < ivIdx; i++) {
            memcpy((unsigned char*)iv[i]->data, pt, iv[i]->dataSz);
            pt += iv[i]->dataSz;
            ASP_FlushCaches(iv[i]->data, iv[i]->dataSz);
        }
    }

    return Success;
}


/******************************************************************************
  CAAM SHA Operations
  ****************************************************************************/
static int shaSize(struct DescStruct* desc)
{
    /* sanity check on dataSz for context */
    switch (desc->type) {
        case CAAM_MD5:
            return CAAM_MD5_CTXSZ;

        case CAAM_SHA:
            return CAAM_SHA_CTXSZ;

        case CAAM_SHA224:
            return CAAM_SHA224_CTXSZ;

        case CAAM_SHA256:
            return CAAM_SHA256_CTXSZ;

        case CAAM_SHA384:
            return CAAM_SHA384_CTXSZ;

        case CAAM_SHA512:
            return CAAM_SHA512_CTXSZ;

        default:
            return 0;
    }
}

/* SHA operations
 * start: the index to start traversing through buffers. It's needed to allow
 *       for HMAC to reuse this code.
 *
 * return Success on success. All other return values are considered a fail
 *         case.
 */
static Error caamSha(struct DescStruct* desc, int start)
{
    struct buffer* ctx[3];
    Error err;
    UINT4 i;
    int sz     = 0;
    int ctxIdx = 0;
    int offset = 0;

    int ctxSz = shaSize(desc);

    /* get context */
    for (i = start; i < desc->DescriptorCount; i++) {
        struct buffer* buf = &desc->buf[i];
        unsigned char* local = (unsigned char*)desc->iv;

        if (sz < ctxSz && sz < (MAX_CTX * sizeof(UINT4))) {
            ctx[ctxIdx] = buf;
            sz += buf->dataSz;

            if (ctx[ctxIdx]->dataSz + offset > (MAX_CTX * sizeof(UINT4))) {
                return SizeIsTooLarge;
            }
            memcpy((unsigned char*)&local[offset], (unsigned char*)ctx[ctxIdx]->data,
            ctx[ctxIdx]->dataSz);
            offset += ctx[ctxIdx]->dataSz;
            ctxIdx++;
        }
        else {
            break;
        }
    }
    if (sz > ctxSz || ctxSz > (MAX_CTX * sizeof(UINT4))) {
        return SizeIsTooLarge;
    }

    ASP_FlushCaches((Address)desc->iv, ctxSz);
    /*Manage Context (current digest + 8 byte running message length)*/
    if ((desc->state & CAAM_ALG_INIT) != CAAM_ALG_INIT) {
        /* don't load into the class 2 context register on inti.
           Found that loading in caused context to not get set. */
        if (desc->idx + 2 > MAX_DESC_SZ) {
            return TransferFailed;
        }
        desc->desc[desc->idx++] = (CAAM_LOAD_CTX | CAAM_CLASS2) + ctxSz;
        desc->desc[desc->idx++] = BSP_VirtualToPhysical((Address)desc->iv);
    }

    /* add operation command */
    desc->desc[desc->idx++] = CAAM_OP | CAAM_CLASS2 | desc->state |
        desc->type;

    /* Check case where there is no input.
       In all cases the FIFO Load should be flushed. */
    if (i == desc->DescriptorCount) {
        desc->lastFifo = desc->idx;
        if (desc->idx + 1 > MAX_DESC_SZ) {
            return TransferFailed;
        }
        desc->desc[desc->idx++] = CAAM_FIFO_L | CAAM_CLASS2 |
        FIFOL_TYPE_MSG | CAAM_IMM;
    }

    /* save index for looping over input */
    desc->headIdx = desc->idx;
    do {
        desc->idx = desc->headIdx; /* reset for each loop */
        if (i < desc->DescriptorCount) {
            /* input must be a multiple of 64 bytes unless in final call */
            if (((desc->state & CAAM_ALG_FINAL) == CAAM_ALG_FINAL)) {
                if (caamAddIO(desc, (CAAM_FIFO_L | CAAM_CLASS2 |
                    FIFOL_TYPE_MSG), 0, 1, &i) < 0) {
                    return TooManyBuffers;
                }
            }
            else {
                if (caamAddIO(desc, (CAAM_FIFO_L | CAAM_CLASS2 |
                    FIFOL_TYPE_MSG), 0, 64, &i) < 0) {
                    return TooManyBuffers;
                }
            }
        }

        desc->desc[desc->lastFifo] |= FIFOL_TYPE_LC2;

        /* set context out */
        if (desc->idx + 2 > MAX_DESC_SZ) {
            return TransferFailed;
        }
        desc->desc[desc->idx++] = CAAM_STORE_CTX | CAAM_CLASS2 + ctxSz;
        desc->desc[desc->idx++] = BSP_VirtualToPhysical(desc->iv);

        if ((err = caamDoJob(desc)) != Success) {
            return err;
        }
        /* flush context output for each loop */
        ASP_FlushCaches((Address)desc->iv, ctxSz);
    } while (i < desc->DescriptorCount);

    /* store context to buffers */
    {
        unsigned char* pt = (unsigned char*)desc->iv;
        for (i = 0; i < ctxIdx; i++) {
            memcpy((unsigned char*)ctx[i]->data, pt, ctx[i]->dataSz);
            pt += ctx[i]->dataSz;
            ASP_FlushCaches(ctx[i]->data, ctx[i]->dataSz);
        }
    }

    return Success;
}


/******************************************************************************
  CAAM TRNG Operations
  ****************************************************************************/

/* If Entropy is not ready then return Waiting */
static Error caamRng(struct DescStruct* desc)
{
    int sz = 0;
    int i;

    Address  reg; /* RTENT reg to read */
    int ofst = sizeof(UINT4);


    /* Check ENT_VAL bit to make sure entropy is ready */
    if ((CAAM_READ(CAAM_RTMCTL) & CAAM_ENTVAL) !=
            CAAM_ENTVAL) {
        return Waiting;
    }

    /* check state of TRNG */
    if ((CAAM_READ(CAAM_RTSTATUS) & 0x0000FFFF) > 0) {
        return Failure;
    }

    /* read entropy from RTENT registers */
    reg = CAAM_RTENT0;

    for (i = 0; i < desc->DescriptorCount; i++) {
        struct buffer* buf = &desc->buf[i];
        unsigned char* local = (unsigned char*)buf->data;
        sz = buf->dataSz;

        while (sz > 3 && reg <= CAAM_RTENT11) {
            *((UINT4*)local) = CAAM_READ(reg);
            reg    += ofst;
            local  += ofst;
            sz     -= ofst;
        }

        if (reg > CAAM_RTENT11 && sz > 0) {
            return SizeIsTooLarge;
        }

        /* handle non word32 size amount left over */
        if (sz > 0) {
            UINT4 tmp = CAAM_READ(reg);
            memcpy(local, (unsigned char*)&tmp, sz);
        }

        ASP_FlushCaches(buf->data, buf->dataSz);
    }


    /* read RTENT11 to trigger new entropy generation */
    if (reg != CAAM_RTENT11) {
        CAAM_READ(CAAM_RTENT11);
    }

    return Success;
}


/******************************************************************************
  IODevice Start, Transfer and Finish Buffer
  ****************************************************************************/
/* args[0] holds the state such as encrypt/decrypt or init/update/final
 * args[1] holds the ctx/key size
 * args[2] holds the input size
 * args[3] dependent on algo (such as AAD size with AES-CCM) */
static Error caamTransferStart(IODeviceVector ioCaam,
    Value type, const volatile Value args[4])
{
    struct CAAM_DEVICE* local = (struct CAAM_DEVICE*)ioCaam;
    struct DescStruct*  desc;

    /* currently only one desc is available for use */
    desc = &local->DescArray[0];

    /* check if the desc is idle before using */
    if (GetIORequestStatus((IORequest)desc) != IdleIORequest) {
         return ResourceNotAvailable;
    }

    desc->idx    = 0;
    desc->output = 0;
    desc->ctxOut = 0;
    desc->outputIdx = 0;
    desc->alignIdx = 0;
    desc->lastFifo = 0;
    desc->state    = args[0];
    desc->ctxSz    = args[1];
    desc->inputSz  = args[2];
    desc->aadSz    = 0;
    desc->desc[desc->idx++] = CAAM_HEAD; /* later will put size to header*/

    switch (type) {
        case CAAM_AESECB:
        case CAAM_AESCBC:
            if (desc->inputSz % 16 != 0) {
                return ArgumentError;
            }
            /* fall through to break */
        case CAAM_AESCTR:
            break;

        case CAAM_AESCCM:
            memset((unsigned char*)desc->aadSzBuf, 0, sizeof(desc->aadSzBuf));
            if (args[3] > 0) {
                /* encode the length in */
                if (args[3] <= 0xFEFF) {
                    unsigned char* pt = (unsigned char*)desc->aadSzBuf;
                    desc->aadSz = 2;
                    pt[0] = ((args[3] & 0xFF00) >> 8);
                    pt[1] =  (args[3] & 0x00FF);
                }
                else if (args[3] <= 0xFFFFFFFF) {
                    unsigned char* pt = (unsigned char*)desc->aadSzBuf;
                    desc->aadSz = 6;
                    pt[0] = 0xFF; pt[1] = 0xFE;
                    pt[2] = ((args[3] & 0xFF000000) >> 24);
                    pt[3] = ((args[3] & 0x00FF0000) >> 16);
                    pt[4] = ((args[3] & 0x0000FF00) >>  8);
                    pt[5] =  (args[3] & 0x000000FF);
                }
            }
            break;

        case CAAM_MD5:
        case CAAM_SHA:
        case CAAM_SHA224:
        case CAAM_SHA256:
        case CAAM_SHA384:
        case CAAM_SHA512:
            break;

        case CAAM_BLOB_ENCAP:
        case CAAM_BLOB_DECAP:
            break;

        case CAAM_ENTROPY:
            break;

        default:
            /* unknown type */
            return UsageNotSupported;
    }

    desc->DescriptorCount = 0;
    desc->type    = type;
    desc->running = true;
    StartIORequest((IORequest)desc);

    /* For now only require READ permissions */
    SetIORequestBufferPermissions((IORequest)desc, MEMORY_READ);
    return Success;
}


static Error caamTransferBuffer(IODeviceVector TheIODeviceVector,
    IORequest req, IODescriptor NewIODescriptor,
    Address data, Address dataSz)
{
    struct DescStruct* desc = (struct DescStruct*)req;
    Error  err;

    switch (desc->type) {
        case CAAM_AESECB:
        case CAAM_AESCTR:
        case CAAM_AESCBC:
        case CAAM_AESCCM:

        case CAAM_MD5:
        case CAAM_SHA:
        case CAAM_SHA224:
        case CAAM_SHA256:
        case CAAM_SHA384:
        case CAAM_SHA512:

        case CAAM_BLOB_ENCAP:
        case CAAM_BLOB_DECAP:
        case CAAM_ENTROPY:
            { /* set buffer for transfer finish */
                struct buffer* buf;
                if (desc->DescriptorCount >= MAX_BUF) {
                    return TooManyBuffers;
                }
                buf = &desc->buf[desc->DescriptorCount];
                buf->data = data;
                buf->dataSz = dataSz;
            }
                err = Success;
            break;

        default:
            err = UsageNotSupported;
    }

    if (err != Success) {
        desc->running = false;
        DismissIORequest(req);
        return err;
    }

    desc->DescriptorCount++;
    return Success;
}


static Error caamTransferFinish(IODeviceVector ioCaam, IORequest req)
{
    struct DescStruct* desc = (struct DescStruct*)req;
    Error ret;

    /* construct desc */
    switch (desc->type) {
        case CAAM_AESECB:
        case CAAM_AESCTR:
        case CAAM_AESCBC:
            ret = caamAes(desc);
            break;

        case CAAM_AESCCM:
            ret = caamAead(desc);
            break;

        case CAAM_MD5:
        case CAAM_SHA:
        case CAAM_SHA224:
        case CAAM_SHA256:
        case CAAM_SHA384:
        case CAAM_SHA512:
            ret = caamSha(desc, 0);
            break;

        case CAAM_ENTROPY:
            ret = caamRng(desc);
            break;

        case CAAM_BLOB_ENCAP:
        case CAAM_BLOB_DECAP:
            ret = caamBlob(desc);
            break;

        default:
            ret = UsageNotSupported;
    }

    desc->running = false;
    DismissIORequest(req);
    return ret;
}


/******************************************************************************
  IODevice Interrupt and Init
  ****************************************************************************/

static Error caamTransferWrite(IODeviceVector ioCaam,
    IORequest req, Value dataSz, const volatile Value *data)
{
    DismissIORequest(req);
    return UsageNotSupported;
}


static void caamTransferAbort(IODeviceVector ioCaam, IORequest req)
{
    DismissIORequest(req);
}


static void caamTransferRecall(IODeviceVector ioCaam, IODescriptor req)
{

}


static void HandleInterrupt(Address id)
{
    struct CAAM_DEVICE* local = (struct CAAM_DEVICE*)id;
    Value InterruptStatus = INTERRUPT_AtomicWrite(&local->InterruptStatus, 0);
    int i;

    /* Loop through descriptors and try to dismiss them */
    for (i = 0; i < DESC_COUNT; i++) {
        struct DescStruct* desc = &local->DescArray[i];
        if (InterruptStatus & (1 << i)) {
            desc->running = false;
            if (GetIORequestStatus((IORequest)desc) == IORequestSuspended) {
                ContinueIORequest((IORequest)desc);
            }
            else {
                DismissIORequest((IORequest)desc);
            }
        }
    }
}


static Error caamCreate(IODeviceVector ioCaam)
{
    return Success;
}


void  InitCAAM(void)
{
    /* get IO vector and set it up */
    IODeviceVector ioCaam = &caam.caamVector;
    unsigned int reg;
    int   i;
    Error ret;


    ioCaam->Create         = &caamCreate;
    ioCaam->ReadRegister   = &caamReadRegister;
    ioCaam->WriteRegister  = &caamWriteRegister;

    ioCaam->TransferStart  = &caamTransferStart;
    ioCaam->TransferBuffer = &caamTransferBuffer;
    ioCaam->TransferWrite  = &caamTransferWrite;
    ioCaam->TransferFinish = &caamTransferFinish;
    ioCaam->TransferAbort  = &caamTransferAbort;
    ioCaam->TransferRecall = &caamTransferRecall;
#ifdef HARDWARE_CACHE_COHERENCY
    ioCaam->IOSynchronizationNotRequired = 1;
#endif

    RegisterIODeviceVector(ioCaam, DRIVER_NAME);
    RequestIOTerminationTask(ioCaam, 10);

    /* Initialize descriptors */
    for (i = 0; i < BUFFER_COUNT; i++) {
        InitializeIODescriptor(ioCaam, &caam.IODescriptorArray[i]);
    }

    /* Initialize Descriptors */
    for (i = 0; i < DESC_COUNT; i++) {
         InitializeIORequest(ioCaam, &caam.DescArray[i].TheIORequest,
                             IOREQUEST_STANDARD);
         caam.DescArray[i].running = false;
         caam.DescArray[i].caam    = &caam;
    }


    /* call interrupt to make IORequests available */
    caam.InterruptStatus = 0;
    INTERRUPT_InitCall(&caam.HandleInterruptCall,
        &HandleInterrupt, "Start up CAAM IORequest");

    /* set clock speed for CAAM. Setting it here to allow for restricting
       access */
    #define REGS_CCM_BASE     (0xf20c4000)
    #define HW_CCM_CCGR0_ADDR (0xf20c4068)
    #define CG(x) (3 << (x*2))

    reg = CG(6) | CG(5) | CG(4);
    *(volatile unsigned int*)HW_CCM_CCGR0_ADDR =
    *(volatile unsigned int*)HW_CCM_CCGR0_ADDR | reg;

    /* set up job ring */

    /* @TODO create partition in physical memory for job rings
       current partition security is set to the default */
    for (i = 1; i < CAAM_PAGE_MAX; i++) {
        ret = caamCreatePartition(i, i);
        if (ret == 0) {
            break;
        }

        if (ret != MemoryMapMayNotBeEmpty) {
            INTERRUPT_Panic();
        }
    }

    if (ret != 0) {
        INTERRUPT_Panic();
    }

    caam.ring.page = i;
    caam.ring.JobIn  =  (CAAM_PAGE + (i << 12));
    caam.ring.JobOut  = caam.ring.JobIn  + 16;
    caam.ring.Desc    = caam.ring.JobOut + 16;

    /* set physical address of job rings */
    CAAM_WRITE(CAAM_IRBAR0, caam.ring.JobIn  ^ 0xF0000000);
    CAAM_WRITE(CAAM_ORBAR0, caam.ring.JobOut ^ 0xF0000000);

    /* Initialize job ring sizes to 1 */
    CAAM_WRITE(CAAM_IRSR0, 1);
    CAAM_WRITE(CAAM_ORSR0, 1);

    /* set DECO watchdog to time out and flush jobs that cause the DECO to hang */
    CAAM_WRITE((CAAM_BASE | 0x0004), CAAM_READ(CAAM_BASE | 0x0004) | 0x40000000);

    /* start up RNG if not already started */
    if (caamInitRng(&caam) != 0) {
        INTERRUPT_Panic();
    }
}

void (*__ghsentry_bspuserinit_InitCAAM)(void) = &InitCAAM;

#endif /* INTEGRITY */
