b3f8eb81创建于 4月21日历史提交
/****************************************************************************
 * crypto/cryptodev.c
 *
 * SPDX-License-Identifier: BSD-2-Clause
 * SPDX-FileCopyrightText: Copyright (c) 2001 Theo de Raadt
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdbool.h>
#include <string.h>
#include <poll.h>
#include <errno.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/crypto/crypto.h>
#include <nuttx/drivers/drivers.h>
#include <nuttx/percpu.h>

#include <crypto/xform.h>
#include <crypto/cryptodev.h>
#include <crypto/cryptosoft.h>

/****************************************************************************
 * Public Data
 ****************************************************************************/

DECLARE_PER_CPU_BMP(FAR struct cryptocap *, crypto_drivers);
#define crypto_drivers this_cpu_var_bmp(crypto_drivers)

DECLARE_PER_CPU_BMP(int, crypto_drivers_num);
#define crypto_drivers_num this_cpu_var_bmp(crypto_drivers_num)

int usercrypto = 1;         /* userland may do crypto requests */
int userasymcrypto = 1;     /* userland may do asymmetric crypto reqs */
#ifdef CONFIG_CRYPTO_CRYPTODEV_SOFTWARE_CRYPTO
int cryptodevallowsoft = 1; /* 0 is only use hardware crypto
                             * 1 is use hardware & software crypto
                             */
#else
int cryptodevallowsoft = 0;
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct csession
{
  TAILQ_ENTRY(csession) next;
  uint64_t sid;
  uint32_t ses;

  uint32_t cipher;
  uint32_t mac;
  bool txform;
  bool thash;

  caddr_t key;
  int keylen;
  u_char tmp_iv[EALG_MAX_BLOCK_LEN];

  caddr_t mackey;
  int mackeylen;
  int error;
};

struct fcrypt
{
  TAILQ_HEAD(csessionlist, csession) csessions;
  TAILQ_HEAD(cryptkoplist, cryptkop) crpk_ret;
  int sesn;
  FAR struct pollfd *fds;
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Character driver methods */

static ssize_t cryptof_read(FAR struct file *filep,
                            FAR char *buffer, size_t len);
static ssize_t cryptof_write(FAR struct file *filep,
                             FAR const char *buffer, size_t len);
static int cryptof_ioctl(FAR struct file *filep,
                         int cmd, unsigned long arg);
static int cryptof_poll(FAR struct file *filep,
                        FAR struct pollfd *fds, bool setup);
static int cryptof_open(FAR struct file *filep);
static int cryptof_close(FAR struct file *filep);

static int cryptoopen(FAR struct file *filep);
static ssize_t cryptoread(FAR struct file *filep,
                          FAR char *buffer, size_t len);
static ssize_t cryptowrite(FAR struct file *filep,
                           FAR const char *buffer, size_t len);
static int cryptoclose(FAR struct file *filep);
static int cryptoioctl(FAR struct file *filep, int cmd, unsigned long arg);

static FAR struct csession *csefind(FAR struct fcrypt *, u_int);
static int csedelete(FAR struct fcrypt *, FAR struct csession *);
static FAR struct csession *cseadd(FAR struct fcrypt *,
                                   FAR struct csession *);
static FAR struct csession *csecreate(FAR struct fcrypt *, uint64_t,
                                      caddr_t, uint64_t,
                                      caddr_t, uint64_t, uint32_t,
                                      uint32_t, bool, bool);
static int csefree(FAR struct csession *);

static int cryptodev_op(FAR struct csession *,
                        FAR struct crypt_op *);
static int cryptodev_key(FAR struct fcrypt *, FAR struct crypt_kop *);
static int cryptodevkey_cb(FAR struct cryptkop *);
static int cryptodev_getkeystatus(FAR struct fcrypt *,
                                  FAR struct crypt_kop *);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct file_operations g_cryptofops =
{
  cryptof_open,        /* open   */
  cryptof_close,       /* close  */
  cryptof_read,        /* read   */
  cryptof_write,       /* write  */
  NULL,                /* seek   */
  cryptof_ioctl,       /* ioctl  */
  NULL,                /* mmap   */
  NULL,                /* truncate */
  cryptof_poll         /* poll   */
};

static const struct file_operations g_cryptoops =
{
  cryptoopen,          /* open   */
  cryptoclose,         /* close  */
  cryptoread,          /* read   */
  cryptowrite,         /* write  */
  NULL,                /* seek   */
  cryptoioctl,         /* ioctl  */
  NULL,                /* mmap   */
  NULL,                /* truncate */
  NULL                 /* poll   */
};

static struct inode g_cryptoinode =
{
  .i_flags = FSNODEFLAG_TYPE_DRIVER,
  .i_crefs = 1,
  .u.i_ops = &g_cryptofops
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/* ARGSUSED */

static ssize_t cryptof_read(FAR struct file *filep,
                            FAR char *buffer, size_t len)
{
  return -EIO;
}

/* ARGSUSED */

static ssize_t cryptof_write(FAR struct file *filep,
                             FAR const char *buffer, size_t len)
{
  return -EIO;
}

/* ARGSUSED */

static int cryptof_ioctl(FAR struct file *filep,
                         int cmd, unsigned long arg)
{
  struct cryptoini cria;
  struct cryptoini crie;
  FAR struct fcrypt *fcr = filep->f_priv;
  FAR struct csession *cse;
  FAR struct session_op *sop;
  FAR struct crypt_op *cop;
  bool txform = false;
  bool thash = false;
  uint64_t sid;
  uint32_t ses;
  int error = 0;

  switch (cmd)
    {
      case CIOCGSESSION:
        sop = (FAR struct session_op *)arg;
        switch (sop->cipher)
          {
            case 0:
              break;
            case CRYPTO_3DES_CBC:
            case CRYPTO_BLF_CBC:
            case CRYPTO_CAST_CBC:
            case CRYPTO_AES_CBC:
            case CRYPTO_AES_192_CBC:
            case CRYPTO_AES_256_CBC:
            case CRYPTO_AES_CMAC:
            case CRYPTO_AES_CTR:
            case CRYPTO_AES_XTS:
            case CRYPTO_AES_OFB:
            case CRYPTO_AES_CFB_8:
            case CRYPTO_AES_CFB_128:
            case CRYPTO_NULL:
              txform = true;
              break;
            default:
              return -EINVAL;
          }

        switch (sop->mac)
          {
            case 0:
              break;
            case CRYPTO_MD5_HMAC:
            case CRYPTO_SHA1_HMAC:
            case CRYPTO_RIPEMD160_HMAC:
            case CRYPTO_SHA2_256_HMAC:
            case CRYPTO_SHA2_384_HMAC:
            case CRYPTO_SHA2_512_HMAC:
            case CRYPTO_AES_128_GMAC:
            case CRYPTO_AES_128_CMAC:
            case CRYPTO_MD5:
            case CRYPTO_POLY1305:
            case CRYPTO_RIPEMD160:
            case CRYPTO_SHA1:
            case CRYPTO_SHA2_224:
            case CRYPTO_SHA2_256:
            case CRYPTO_SHA2_384:
            case CRYPTO_SHA2_512:
            case CRYPTO_CRC8:
            case CRYPTO_CRC16:
            case CRYPTO_CRC32:
              thash = true;
              break;
            default:
              return -EINVAL;
          }

        bzero(&crie, sizeof(crie));
        bzero(&cria, sizeof(cria));

        if (txform)
          {
            crie.cri_alg = sop->cipher;
            crie.cri_klen = sop->keylen * 8;
            crie.cri_op = sop->op;
            if (sop->flags & SOP_F_KEYID)
              {
                crie.cri_flags |= CRD_F_KEYID;
              }

            crie.cri_key = kmm_malloc(crie.cri_klen / 8);
            if (crie.cri_key == NULL)
              {
                error = -ENOMEM;
                goto bail;
              }

            memcpy(crie.cri_key, sop->key, crie.cri_klen / 8);
            if (thash)
              {
                crie.cri_next = &cria;
              }
          }

        if (thash)
          {
            cria.cri_alg = sop->mac;
            cria.cri_sid = -1;
            cria.cri_klen = sop->mackeylen * 8;
            if (sop->flags & SOP_F_KEYID)
              {
                cria.cri_flags |= CRD_F_KEYID;
              }

            if (cria.cri_klen)
              {
                cria.cri_key = kmm_malloc(cria.cri_klen / 8);
                if (cria.cri_key == NULL)
                  {
                    error = -ENOMEM;
                    goto bail;
                  }

                memcpy(cria.cri_key, sop->mackey, cria.cri_klen / 8);
              }
          }

        error = crypto_newsession(&sid, txform ? &crie : &cria,
                                  !cryptodevallowsoft);

        if (error)
          {
            goto bail;
          }

        cse = csecreate(fcr, sid, crie.cri_key, crie.cri_klen,
              cria.cri_key, cria.cri_klen, sop->cipher, sop->mac, txform,
              thash);

        if (cse == NULL)
          {
            crypto_freesession(sid);
            error = -EINVAL;
            goto bail;
          }

        sop->ses = cse->ses;

bail:
        if (error)
          {
            if (crie.cri_key)
              {
                explicit_bzero(crie.cri_key, crie.cri_klen / 8);
                kmm_free(crie.cri_key);
              }

            if (cria.cri_key)
              {
                explicit_bzero(cria.cri_key, cria.cri_klen / 8);
                kmm_free(cria.cri_key);
              }
          }

        break;
      case CIOCFSESSION:
        ses = *(FAR uint32_t *)arg;
        cse = csefind(fcr, ses);
        if (cse == NULL)
          {
            return -EINVAL;
          }

        csedelete(fcr, cse);
        error = csefree(cse);
        break;
      case CIOCCRYPT:
        cop = (FAR struct crypt_op *)arg;
        cse = csefind(fcr, cop->ses);
        if (cse == NULL)
          {
            return -EINVAL;
          }

        error = cryptodev_op(cse, cop);
        break;
      case CIOCKEY:
        error = cryptodev_key(fcr, (FAR struct crypt_kop *)arg);
        break;
      case CIOCKEYRET:
        error = cryptodev_getkeystatus(fcr, (FAR struct crypt_kop *)arg);
        break;
      case CIOCASYMFEAT:
        error = crypto_getfeat((FAR int *)arg);
        break;
      default:
        error = -ENOTTY;
    }

  return error;
}

static int cryptodev_op(FAR struct csession *cse,
                        FAR struct crypt_op *cop)
{
  struct cryptop crp;
  struct cryptodesc crda;
  struct cryptodesc crde;
  int error = OK;
  uint32_t hid;

  /* number of requests, not logical and */

  bzero(&crp, sizeof(struct cryptop));
  bzero(&crda, sizeof(struct cryptodesc));
  bzero(&crde, sizeof(struct cryptodesc));
  if (cse->thash)
    {
      crp.crp_desc = &crda;
      crda.crd_skip = 0;
      crda.crd_len = cop->len;
      crda.crd_inject = 0;

      crda.crd_alg = cse->mac;
      crda.crd_key = cse->mackey;
      crda.crd_klen = cse->mackeylen * 8;
      if (cop->flags & COP_FLAG_UPDATE)
        {
          crda.crd_flags |= CRD_F_UPDATE;
        }
      else
        {
          crda.crd_flags &= ~CRD_F_UPDATE;
        }
    }

  if (cse->txform)
    {
      if (cse->thash)
        {
          crda.crd_next = &crde;
        }
      else
        {
          crp.crp_desc = &crde;
        }

      if (cop->op == COP_ENCRYPT)
        {
          crde.crd_flags |= CRD_F_ENCRYPT;
        }
      else
        {
          crde.crd_flags &= ~CRD_F_ENCRYPT;
        }

      if (cop->flags & COP_FLAG_UPDATE)
        {
          crde.crd_flags |= CRD_F_UPDATE;
        }

      crde.crd_len = cop->len;
      crde.crd_inject = 0;
      crde.crd_alg = cse->cipher;
      crde.crd_key = cse->key;
      crde.crd_klen = cse->keylen * 8;
    }

  crp.crp_ilen = cop->len;
  crp.crp_olen = cop->olen;
  crp.crp_buf = cop->src;
  crp.crp_sid = cse->sid;
  crp.crp_opaque = cse;

  if (cop->iv)
    {
      crp.crp_iv = cop->iv;
      crp.crp_ivlen = cop->ivlen;
    }

  if (cop->dst)
    {
      crp.crp_dst = cop->dst;
    }

  if (cop->mac)
    {
      crp.crp_mac = cop->mac;
    }

  /* try the fast path first */

  crp.crp_flags = CRYPTO_F_IOV | CRYPTO_F_NOQUEUE;
  hid = (crp.crp_sid >> 32) & 0xffffffff;
  if (hid >= crypto_drivers_num ||
      (crypto_drivers[hid].cc_flags & CRYPTOCAP_F_SOFTWARE) ||
      crypto_drivers[hid].cc_process == NULL)
    {
      crp.crp_flags = CRYPTO_F_IOV;
      crypto_invoke(&crp);
    }
  else
    {
      error = crypto_drivers[hid].cc_process(&crp);
    }

  if ((cop->flags & COP_FLAG_UPDATE) == 0)
    {
      crde.crd_flags &= ~CRD_F_IV_EXPLICIT;
    }

  if (!error && crp.crp_etype != 0)
    {
      error = crp.crp_etype;
    }

  return error;
}

static int cryptodev_key(FAR struct fcrypt *fcr, FAR struct crypt_kop *kop)
{
  FAR struct cryptkop *krp_async = NULL;
  FAR struct cryptkop *krp = NULL;
  int error = -EINVAL;
  int in;
  int out;
  int size;
  int i;

  if (kop->crk_iparams + kop->crk_oparams > CRK_MAXPARAM)
    {
      return -EFBIG;
    }

  in = kop->crk_iparams;
  out = kop->crk_oparams;
  switch (kop->crk_op)
    {
      case CRK_MOD_EXP:
        if (in == 3 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_MOD_EXP_CRT:
        if (in == 9 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_DSA_SIGN:
        if (in == 5 && out == 2)
          {
            break;
          }

        return -EINVAL;
      case CRK_DSA_VERIFY:
        if (in == 7 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_DH_COMPUTE_KEY:
        if (in == 3 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_DH_MAKE_PUBLIC:
        if (in == 2 && out == 2)
          {
            break;
          }

        return -EINVAL;
      case CRK_RSA_PKCS15_SIGN:
        if (in == 9 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_RSA_PKCS15_VERIFY:
        if (in == 5 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_RSA_PSS_SIGN:
        if (in == 3 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_RSA_PSS_VERIFY:
        if (in == 4 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_ECDSA_SECP256R1_SIGN:
        if (in == 2 && out == 2)
          {
            break;
          }

        return -EINVAL;
      case CRK_ECDSA_SECP256R1_VERIFY:
        if (in == 6 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_ECDSA_SECP256R1_GENKEY:
        if (in == 0 && out == 4)
          {
            break;
          }

        return -EINVAL;

      /* key management */

      case CRK_ALLOCATE_KEY:

      /* outparam: keyid */

        if (in == 0 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_VALIDATE_KEYID:
      case CRK_DELETE_KEY:
      case CRK_SAVE_KEY:
      case CRK_LOAD_KEY:
      case CRK_UNLOAD_KEY:

      /* inparam: keyid */

        if (in == 1 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_IMPORT_KEY:

      /* inparam: keyid, raw data */

      case CRK_GENERATE_AES_KEY:

      /* inparam: keyid, keylen 16/24/32(128/192/256) */

        if (in == 2 && out == 0)
          {
            break;
          }

        return -EINVAL;
      case CRK_EXPORT_KEY:
      case CRK_EXPORT_PUBLIC_KEY:

      /* inparam: keyid, outparam: key data */

        if (in == 1 && out == 1)
          {
            break;
          }

        return -EINVAL;
      case CRK_GENERATE_RSA_KEY:
      case CRK_GENERATE_SECP256R1_KEY:

      /* 1 inparam : keypair id or private keyid
       * 2 inparam : private keyid, public keyid
       */

        if ((in == 1 || in == 2) && out == 0)
          {
            break;
          }

        return -EINVAL;
      default:
        return -EINVAL;
    }

  krp = kmm_zalloc(sizeof *krp);
  if (krp == NULL)
    {
      return -ENOMEM;
    }

  krp->krp_op = kop->crk_op;
  krp->krp_iparams = kop->crk_iparams;
  krp->krp_oparams = kop->crk_oparams;
  krp->krp_keytype = kop->crk_keytype;
  krp->krp_optype = kop->crk_optype;
  krp->krp_padding = kop->crk_padding;
  krp->krp_hash = kop->crk_hash;
  krp->krp_status = 0;
  krp->krp_flags = kop->crk_flags;
  krp->krp_reqid = kop->crk_reqid;

  if (krp->krp_flags & CRYPTO_F_CBIMM)
    {
      if (kop->crk_arg == NULL)
        {
          error = -EINVAL;
          goto fail;
        }

      krp_async = (FAR struct cryptkop *)kop->crk_arg;
      krp_async->krp_fcr = fcr;
      krp_async->krp_callback = cryptodevkey_cb;
      krp->krp_opaque = krp_async;
    }

  for (i = 0; i < CRK_MAXPARAM; i++)
    {
      krp->krp_param[i].crp_nbits = kop->crk_param[i].crp_nbits;
      if (kop->crk_param[i].crp_nbits > 65536)
        {
          /* XXX how big do we need to support? */

          goto fail;
        }
    }

  for (i = 0; i < krp->krp_iparams + krp->krp_oparams; i++)
    {
      size = (krp->krp_param[i].crp_nbits + 7) / 8;
      if (size == 0)
        {
          continue;
        }

      krp->krp_param[i].crp_p = kmm_zalloc(size);
      if (i >= krp->krp_iparams)
        {
          continue;
        }

      memcpy(krp->krp_param[i].crp_p, kop->crk_param[i].crp_p, size);
    }

  error = crypto_kinvoke(krp);
  if (error)
    {
      goto fail;
    }

  if (krp->krp_status != 0)
    {
      error = krp->krp_status;
      goto fail;
    }

  for (i = krp->krp_iparams; i < krp->krp_iparams + krp->krp_oparams; i++)
    {
      size = (krp->krp_param[i].crp_nbits + 7) / 8;
      if (size == 0)
        {
          continue;
        }

      memcpy(kop->crk_param[i].crp_p,
             krp->krp_param[i].crp_p, size);
    }

fail:
  if (krp)
    {
      kop->crk_status = krp->krp_status;
      for (i = 0; i < CRK_MAXPARAM; i++)
        {
          if (krp->krp_param[i].crp_p)
            {
              explicit_bzero(krp->krp_param[i].crp_p,
                  (krp->krp_param[i].crp_nbits + 7) / 8);
              kmm_free(krp->krp_param[i].crp_p);
            }
        }

      kmm_free(krp);
    }

  return error;
}

static int cryptodevkey_cb(FAR struct cryptkop *krp)
{
  TAILQ_INSERT_TAIL(&krp->krp_fcr->crpk_ret, krp, krp_next);
  if (krp->krp_fcr->fds != NULL)
    {
      poll_notify(&krp->krp_fcr->fds, 1, POLLIN);
    }

  return OK;
}

static int cryptodev_getkeystatus(struct fcrypt *fcr, struct crypt_kop *ret)
{
  FAR struct cryptkop *krp = NULL;
  int i;
  int size;

  if (TAILQ_EMPTY(&fcr->crpk_ret))
    {
      return -EAGAIN;
    }

  TAILQ_FOREACH(krp, &fcr->crpk_ret, krp_next)
    {
      if (krp->krp_reqid == ret->crk_reqid)
        {
          break;
        }
    }

  if (krp == NULL)
    {
      return -EINVAL;
    }

  TAILQ_REMOVE(&fcr->crpk_ret, krp, krp_next);
  ret->crk_op = krp->krp_op;
  ret->crk_status = krp->krp_status;
  ret->crk_iparams = krp->krp_iparams;
  ret->crk_oparams = krp->krp_oparams;
  for (i = 0; i < krp->krp_iparams + krp->krp_oparams; i++)
    {
      size = (krp->krp_param[i].crp_nbits + 7) / 8;

      /* copy result into oparams */

      if (i < ret->crk_iparams || size == 0)
        {
          continue;
        }

      memcpy(ret->crk_param[i].crp_p, krp->krp_param[i].crp_p, size);
    }

  return OK;
}

/* ARGSUSED */

static int cryptof_poll(FAR struct file *filep,
                        FAR struct pollfd *fds, bool setup)
{
  FAR struct fcrypt *fcr = filep->f_priv;

  if (fcr == NULL || fds == NULL)
    {
      return -EINVAL;
    }

  if (setup)
    {
      if (!TAILQ_EMPTY(&fcr->crpk_ret))
        {
          poll_notify(&fds, 1, POLLIN);
          return OK;
        }

      if (fcr->fds)
        {
          return -EBUSY;
        }

      fcr->fds = fds;
    }
  else
    {
      fcr->fds = NULL;
    }

  return OK;
}

/* ARGSUSED */

static int cryptof_close(FAR struct file *filep)
{
  FAR struct fcrypt *fcr = filep->f_priv;
  FAR struct csession *cse;

  while ((cse = TAILQ_FIRST(&fcr->csessions)))
    {
      TAILQ_REMOVE(&fcr->csessions, cse, next);
      (void)csefree(cse);
    }

  kmm_free(fcr);
  filep->f_priv = NULL;
  return 0;
}

/* Clone csessions into a new fd */

static int cryptof_open(FAR struct file *filep)
{
  FAR struct fcrypt *fcr = filep->f_priv;
  FAR struct fcrypt *fcrd = NULL;
  FAR struct csession *cse;
  FAR struct csession *csed;
  struct cryptoini cria;
  struct cryptoini crie;
  uint64_t sid;
  int ret = 0;

  if (fcr == NULL)
    {
      return -EINVAL;
    }

  /* A 'struct fcrypt' is bound to the fd of the cryptodev,
   * which stores a list of sessions. Each encryption operation
   * would create a context and get a session id, which can be
   * used to find the specific encryption operation. Therefore,
   * in order to complete the copy operation, it is necessary to
   * create same session based on the copy fd and obtain the newly
   * generated session id.
   */

  fcrd = kmm_zalloc(sizeof(struct fcrypt));
  if (fcrd == NULL)
    {
      return -ENOMEM;
    }

  TAILQ_INIT(&fcrd->csessions);
  TAILQ_FOREACH(cse, &fcr->csessions, next)
    {
      bzero(&crie, sizeof(crie));
      bzero(&cria, sizeof(cria));
      if (cse->txform)
        {
          crie.cri_alg = cse->cipher;
          crie.cri_klen = cse->keylen * 8;

          crie.cri_key = kmm_malloc(cse->keylen);
          if (crie.cri_key == NULL)
            {
              ret = -ENOMEM;
              goto bail;
            }

          memcpy(crie.cri_key, cse->key, cse->keylen);
          if (cse->thash)
            {
              crie.cri_next = &cria;
            }
        }

      if (cse->thash)
        {
          cria.cri_alg = cse->mac;
          cria.cri_sid = cse->sid;
          cria.cri_klen = cse->mackeylen * 8;

          if (cria.cri_klen)
            {
              cria.cri_key = kmm_malloc(cse->mackeylen);
              if (cria.cri_key == NULL)
                {
                  ret = -ENOMEM;
                  goto bail;
                }

              memcpy(cria.cri_key, cse->mackey, cse->mackeylen);
            }
        }

      ret = crypto_newsession(&sid, cse->txform ? &crie : &cria,
                              !cryptodevallowsoft);
      if (ret < 0)
        {
          goto bail;
        }

      csed = csecreate(fcrd, sid, crie.cri_key, crie.cri_klen,
                        cria.cri_key, cria.cri_klen,
                        cse->cipher, cse->mac, cse->txform,
                        cse->thash);
      if (csed == NULL)
        {
          crypto_freesession(sid);
          ret = -EINVAL;
          goto bail;
        }

      csed->ses = cse->ses;
    }

  filep->f_priv = fcrd;
  return 0;

bail:
  if (crie.cri_key)
    {
      explicit_bzero(crie.cri_key, crie.cri_klen / 8);
      kmm_free(crie.cri_key);
    }

  if (cria.cri_key)
    {
      explicit_bzero(cria.cri_key, cria.cri_klen / 8);
      kmm_free(cria.cri_key);
    }

  free(fcrd);
  return ret;
}

static int cryptoopen(FAR struct file *filep)
{
  if (usercrypto == 0)
    {
      return -ENXIO;
    }

  return 0;
}

static ssize_t cryptoread(FAR struct file *filep,
                          FAR char *buffer, size_t len)
{
  return 0;
}

static ssize_t cryptowrite(FAR struct file *filep,
                           FAR const char *buffer, size_t len)
{
  return len;
}

static int cryptoclose(FAR struct file *filep)
{
  return 0;
}

static int cryptoioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct fcrypt *fcr;
  int fd;
  int error = 0;

  switch (cmd)
    {
      case CRIOGET:
        fcr = kmm_zalloc(sizeof(struct fcrypt));
        if (fcr == NULL)
          {
            return -ENOMEM;
          }

        TAILQ_INIT(&fcr->csessions);
        TAILQ_INIT(&fcr->crpk_ret);

        fd = file_allocate_from_inode(&g_cryptoinode, 0, 0, fcr, 0);
        if (fd < 0)
          {
            kmm_free(fcr);
            return fd;
          }

        fcr->sesn = 0;
        *(FAR uint32_t *)arg = fd;
        break;
      default:
        error = -EINVAL;
        break;
    }

  return error;
}

static FAR struct csession *csefind(FAR struct fcrypt *fcr, u_int ses)
{
  FAR struct csession *cse;

  TAILQ_FOREACH(cse, &fcr->csessions, next)
  if (cse->ses == ses)
    {
      return cse;
    }

  return NULL;
}

static int csedelete(FAR struct fcrypt *fcr, FAR struct csession *cse_del)
{
  FAR struct csession *cse;

  TAILQ_FOREACH(cse, &fcr->csessions, next)
    {
      if (cse == cse_del)
        {
          TAILQ_REMOVE(&fcr->csessions, cse, next);
          return 1;
        }
    }

  return 0;
}

static FAR struct csession *cseadd(FAR struct fcrypt *fcr,
                                   FAR struct csession *cse)
{
  TAILQ_INSERT_TAIL(&fcr->csessions, cse, next);
  cse->ses = fcr->sesn++;
  return cse;
}

static FAR struct csession *csecreate(FAR struct fcrypt *fcr, uint64_t sid,
                                      caddr_t key, uint64_t keylen,
                                      caddr_t mackey, uint64_t mackeylen,
                                      uint32_t cipher, uint32_t mac,
                                      bool txform, bool thash)
{
  FAR struct csession *cse;

  cse = kmm_malloc(sizeof(struct csession));
  if (cse != NULL)
    {
      cse->key = key;
      cse->keylen = keylen / 8;
      cse->mackey = mackey;
      cse->mackeylen = mackeylen / 8;
      cse->sid = sid;
      cse->cipher = cipher;
      cse->mac = mac;
      cse->txform = txform;
      cse->thash = thash;
      cse->error = 0;
      cseadd(fcr, cse);
    }

  return cse;
}

static int csefree(FAR struct csession *cse)
{
  int error;

  error = crypto_freesession(cse->sid);
  if (cse->key)
    {
      kmm_free(cse->key);
    }

  if (cse->mackey)
    {
      kmm_free(cse->mackey);
    }

  kmm_free(cse);
  return error;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

void devcrypto_register(void)
{
  register_driver("/dev/crypto", &g_cryptoops, 0666, NULL);

#ifdef CONFIG_CRYPTO_CRYPTODEV_SOFTWARE_CRYPTO
  swcr_init();
#endif

#ifdef CONFIG_CRYPTO_CRYPTODEV_SOFTWARE_KEYMGMT
  swkey_init();
#endif

#ifdef CONFIG_CRYPTO_CRYPTODEV_HARDWARE
  hwcr_init();
#endif
}