rename target/linux/generic-2.6 to generic
[15.05/openwrt.git] / target / linux / generic / files / crypto / ocf / safe / safe.c
diff --git a/target/linux/generic/files/crypto/ocf/safe/safe.c b/target/linux/generic/files/crypto/ocf/safe/safe.c
new file mode 100644 (file)
index 0000000..7e65101
--- /dev/null
@@ -0,0 +1,2288 @@
+/*-
+ * Linux port done by David McCullough <david_mccullough@mcafee.com>
+ * Copyright (C) 2004-2010 David McCullough
+ * The license and original author are listed below.
+ *
+ * Copyright (c) 2003 Sam Leffler, Errno Consulting
+ * Copyright (c) 2003 Global Technology Associates, Inc.
+ * All rights reserved.
+ *
+ * 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 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 AUTHOR 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.
+ *
+__FBSDID("$FreeBSD: src/sys/dev/safe/safe.c,v 1.18 2007/03/21 03:42:50 sam Exp $");
+ */
+
+#ifndef AUTOCONF_INCLUDED
+#include <linux/config.h>
+#endif
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <linux/version.h>
+#include <linux/skbuff.h>
+#include <asm/io.h>
+
+/*
+ * SafeNet SafeXcel-1141 hardware crypto accelerator
+ */
+
+#include <cryptodev.h>
+#include <uio.h>
+#include <safe/safereg.h>
+#include <safe/safevar.h>
+
+#if 1
+#define        DPRINTF(a)      do { \
+                                               if (debug) { \
+                                                       printk("%s: ", sc ? \
+                                                               device_get_nameunit(sc->sc_dev) : "safe"); \
+                                                       printk a; \
+                                               } \
+                                       } while (0)
+#else
+#define        DPRINTF(a)
+#endif
+
+/*
+ * until we find a cleaner way, include the BSD md5/sha1 code
+ * here
+ */
+#define HMAC_HACK 1
+#ifdef HMAC_HACK
+#define LITTLE_ENDIAN 1234
+#define BIG_ENDIAN 4321
+#ifdef __LITTLE_ENDIAN
+#define BYTE_ORDER LITTLE_ENDIAN
+#endif
+#ifdef __BIG_ENDIAN
+#define BYTE_ORDER BIG_ENDIAN
+#endif
+#include <safe/md5.h>
+#include <safe/md5.c>
+#include <safe/sha1.h>
+#include <safe/sha1.c>
+
+u_int8_t hmac_ipad_buffer[64] = {
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36
+};
+
+u_int8_t hmac_opad_buffer[64] = {
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
+    0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C
+};
+#endif /* HMAC_HACK */
+
+/* add proc entry for this */
+struct safe_stats safestats;
+
+#define debug safe_debug
+int safe_debug = 0;
+module_param(safe_debug, int, 0644);
+MODULE_PARM_DESC(safe_debug, "Enable debug");
+
+static void safe_callback(struct safe_softc *, struct safe_ringentry *);
+static void safe_feed(struct safe_softc *, struct safe_ringentry *);
+#if defined(CONFIG_OCF_RANDOMHARVEST) && !defined(SAFE_NO_RNG)
+static void safe_rng_init(struct safe_softc *);
+int safe_rngbufsize = 8;               /* 32 bytes each read  */
+module_param(safe_rngbufsize, int, 0644);
+MODULE_PARM_DESC(safe_rngbufsize, "RNG polling buffer size (32-bit words)");
+int safe_rngmaxalarm = 8;              /* max alarms before reset */
+module_param(safe_rngmaxalarm, int, 0644);
+MODULE_PARM_DESC(safe_rngmaxalarm, "RNG max alarms before reset");
+#endif /* SAFE_NO_RNG */
+
+static void safe_totalreset(struct safe_softc *sc);
+static int safe_dmamap_aligned(struct safe_softc *sc, const struct safe_operand *op);
+static int safe_dmamap_uniform(struct safe_softc *sc, const struct safe_operand *op);
+static int safe_free_entry(struct safe_softc *sc, struct safe_ringentry *re);
+static int safe_kprocess(device_t dev, struct cryptkop *krp, int hint);
+static int safe_kstart(struct safe_softc *sc);
+static int safe_ksigbits(struct safe_softc *sc, struct crparam *cr);
+static void safe_kfeed(struct safe_softc *sc);
+static void safe_kpoll(unsigned long arg);
+static void safe_kload_reg(struct safe_softc *sc, u_int32_t off,
+                                                               u_int32_t len, struct crparam *n);
+
+static int safe_newsession(device_t, u_int32_t *, struct cryptoini *);
+static int safe_freesession(device_t, u_int64_t);
+static int safe_process(device_t, struct cryptop *, int);
+
+static device_method_t safe_methods = {
+       /* crypto device methods */
+       DEVMETHOD(cryptodev_newsession, safe_newsession),
+       DEVMETHOD(cryptodev_freesession,safe_freesession),
+       DEVMETHOD(cryptodev_process,    safe_process),
+       DEVMETHOD(cryptodev_kprocess,   safe_kprocess),
+};
+
+#define        READ_REG(sc,r)                  readl((sc)->sc_base_addr + (r))
+#define WRITE_REG(sc,r,val)            writel((val), (sc)->sc_base_addr + (r))
+
+#define SAFE_MAX_CHIPS 8
+static struct safe_softc *safe_chip_idx[SAFE_MAX_CHIPS];
+
+/*
+ * split our buffers up into safe DMAable byte fragments to avoid lockup
+ * bug in 1141 HW on rev 1.0.
+ */
+
+static int
+pci_map_linear(
+       struct safe_softc *sc,
+       struct safe_operand *buf,
+       void *addr,
+       int len)
+{
+       dma_addr_t tmp;
+       int chunk, tlen = len;
+
+       tmp = pci_map_single(sc->sc_pcidev, addr, len, PCI_DMA_BIDIRECTIONAL);
+
+       buf->mapsize += len;
+       while (len > 0) {
+               chunk = (len > sc->sc_max_dsize) ? sc->sc_max_dsize : len;
+               buf->segs[buf->nsegs].ds_addr = tmp;
+               buf->segs[buf->nsegs].ds_len  = chunk;
+               buf->segs[buf->nsegs].ds_tlen = tlen;
+               buf->nsegs++;
+               tmp  += chunk;
+               len  -= chunk;
+               tlen = 0;
+       }
+       return 0;
+}
+
+/*
+ * map in a given uio buffer (great on some arches :-)
+ */
+
+static int
+pci_map_uio(struct safe_softc *sc, struct safe_operand *buf, struct uio *uio)
+{
+       struct iovec *iov = uio->uio_iov;
+       int n;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       buf->mapsize = 0;
+       buf->nsegs = 0;
+
+       for (n = 0; n < uio->uio_iovcnt; n++) {
+               pci_map_linear(sc, buf, iov->iov_base, iov->iov_len);
+               iov++;
+       }
+
+       /* identify this buffer by the first segment */
+       buf->map = (void *) buf->segs[0].ds_addr;
+       return(0);
+}
+
+/*
+ * map in a given sk_buff
+ */
+
+static int
+pci_map_skb(struct safe_softc *sc,struct safe_operand *buf,struct sk_buff *skb)
+{
+       int i;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       buf->mapsize = 0;
+       buf->nsegs = 0;
+
+       pci_map_linear(sc, buf, skb->data, skb_headlen(skb));
+
+       for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+               pci_map_linear(sc, buf,
+                               page_address(skb_shinfo(skb)->frags[i].page) +
+                                                       skb_shinfo(skb)->frags[i].page_offset,
+                               skb_shinfo(skb)->frags[i].size);
+       }
+
+       /* identify this buffer by the first segment */
+       buf->map = (void *) buf->segs[0].ds_addr;
+       return(0);
+}
+
+
+#if 0 /* not needed at this time */
+static void
+pci_sync_operand(struct safe_softc *sc, struct safe_operand *buf)
+{
+       int i;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+       for (i = 0; i < buf->nsegs; i++)
+               pci_dma_sync_single_for_cpu(sc->sc_pcidev, buf->segs[i].ds_addr,
+                               buf->segs[i].ds_len, PCI_DMA_BIDIRECTIONAL);
+}
+#endif
+
+static void
+pci_unmap_operand(struct safe_softc *sc, struct safe_operand *buf)
+{
+       int i;
+       DPRINTF(("%s()\n", __FUNCTION__));
+       for (i = 0; i < buf->nsegs; i++) {
+               if (buf->segs[i].ds_tlen) {
+                       DPRINTF(("%s - unmap %d 0x%x %d\n", __FUNCTION__, i, buf->segs[i].ds_addr, buf->segs[i].ds_tlen));
+                       pci_unmap_single(sc->sc_pcidev, buf->segs[i].ds_addr,
+                                       buf->segs[i].ds_tlen, PCI_DMA_BIDIRECTIONAL);
+                       DPRINTF(("%s - unmap %d 0x%x %d done\n", __FUNCTION__, i, buf->segs[i].ds_addr, buf->segs[i].ds_tlen));
+               }
+               buf->segs[i].ds_addr = 0;
+               buf->segs[i].ds_len = 0;
+               buf->segs[i].ds_tlen = 0;
+       }
+       buf->nsegs = 0;
+       buf->mapsize = 0;
+       buf->map = 0;
+}
+
+
+/*
+ * SafeXcel Interrupt routine
+ */
+static irqreturn_t
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+safe_intr(int irq, void *arg)
+#else
+safe_intr(int irq, void *arg, struct pt_regs *regs)
+#endif
+{
+       struct safe_softc *sc = arg;
+       int stat;
+       unsigned long flags;
+
+       stat = READ_REG(sc, SAFE_HM_STAT);
+
+       DPRINTF(("%s(stat=0x%x)\n", __FUNCTION__, stat));
+
+       if (stat == 0)          /* shared irq, not for us */
+               return IRQ_NONE;
+
+       WRITE_REG(sc, SAFE_HI_CLR, stat);       /* IACK */
+
+       if ((stat & SAFE_INT_PE_DDONE)) {
+               /*
+                * Descriptor(s) done; scan the ring and
+                * process completed operations.
+                */
+               spin_lock_irqsave(&sc->sc_ringmtx, flags);
+               while (sc->sc_back != sc->sc_front) {
+                       struct safe_ringentry *re = sc->sc_back;
+
+#ifdef SAFE_DEBUG
+                       if (debug) {
+                               safe_dump_ringstate(sc, __func__);
+                               safe_dump_request(sc, __func__, re);
+                       }
+#endif
+                       /*
+                        * safe_process marks ring entries that were allocated
+                        * but not used with a csr of zero.  This insures the
+                        * ring front pointer never needs to be set backwards
+                        * in the event that an entry is allocated but not used
+                        * because of a setup error.
+                        */
+                       DPRINTF(("%s re->re_desc.d_csr=0x%x\n", __FUNCTION__, re->re_desc.d_csr));
+                       if (re->re_desc.d_csr != 0) {
+                               if (!SAFE_PE_CSR_IS_DONE(re->re_desc.d_csr)) {
+                                       DPRINTF(("%s !CSR_IS_DONE\n", __FUNCTION__));
+                                       break;
+                               }
+                               if (!SAFE_PE_LEN_IS_DONE(re->re_desc.d_len)) {
+                                       DPRINTF(("%s !LEN_IS_DONE\n", __FUNCTION__));
+                                       break;
+                               }
+                               sc->sc_nqchip--;
+                               safe_callback(sc, re);
+                       }
+                       if (++(sc->sc_back) == sc->sc_ringtop)
+                               sc->sc_back = sc->sc_ring;
+               }
+               spin_unlock_irqrestore(&sc->sc_ringmtx, flags);
+       }
+
+       /*
+        * Check to see if we got any DMA Error
+        */
+       if (stat & SAFE_INT_PE_ERROR) {
+               printk("%s: dmaerr dmastat %08x\n", device_get_nameunit(sc->sc_dev),
+                               (int)READ_REG(sc, SAFE_PE_DMASTAT));
+               safestats.st_dmaerr++;
+               safe_totalreset(sc);
+#if 0
+               safe_feed(sc);
+#endif
+       }
+
+       if (sc->sc_needwakeup) {                /* XXX check high watermark */
+               int wakeup = sc->sc_needwakeup & (CRYPTO_SYMQ|CRYPTO_ASYMQ);
+               DPRINTF(("%s: wakeup crypto %x\n", __func__,
+                       sc->sc_needwakeup));
+               sc->sc_needwakeup &= ~wakeup;
+               crypto_unblock(sc->sc_cid, wakeup);
+       }
+       
+       return IRQ_HANDLED;
+}
+
+/*
+ * safe_feed() - post a request to chip
+ */
+static void
+safe_feed(struct safe_softc *sc, struct safe_ringentry *re)
+{
+       DPRINTF(("%s()\n", __FUNCTION__));
+#ifdef SAFE_DEBUG
+       if (debug) {
+               safe_dump_ringstate(sc, __func__);
+               safe_dump_request(sc, __func__, re);
+       }
+#endif
+       sc->sc_nqchip++;
+       if (sc->sc_nqchip > safestats.st_maxqchip)
+               safestats.st_maxqchip = sc->sc_nqchip;
+       /* poke h/w to check descriptor ring, any value can be written */
+       WRITE_REG(sc, SAFE_HI_RD_DESCR, 0);
+}
+
+#define        N(a)    (sizeof(a) / sizeof (a[0]))
+static void
+safe_setup_enckey(struct safe_session *ses, caddr_t key)
+{
+       int i;
+
+       bcopy(key, ses->ses_key, ses->ses_klen / 8);
+
+       /* PE is little-endian, insure proper byte order */
+       for (i = 0; i < N(ses->ses_key); i++)
+               ses->ses_key[i] = htole32(ses->ses_key[i]);
+}
+
+static void
+safe_setup_mackey(struct safe_session *ses, int algo, caddr_t key, int klen)
+{
+#ifdef HMAC_HACK
+       MD5_CTX md5ctx;
+       SHA1_CTX sha1ctx;
+       int i;
+
+
+       for (i = 0; i < klen; i++)
+               key[i] ^= HMAC_IPAD_VAL;
+
+       if (algo == CRYPTO_MD5_HMAC) {
+               MD5Init(&md5ctx);
+               MD5Update(&md5ctx, key, klen);
+               MD5Update(&md5ctx, hmac_ipad_buffer, MD5_HMAC_BLOCK_LEN - klen);
+               bcopy(md5ctx.md5_st8, ses->ses_hminner, sizeof(md5ctx.md5_st8));
+       } else {
+               SHA1Init(&sha1ctx);
+               SHA1Update(&sha1ctx, key, klen);
+               SHA1Update(&sha1ctx, hmac_ipad_buffer,
+                   SHA1_HMAC_BLOCK_LEN - klen);
+               bcopy(sha1ctx.h.b32, ses->ses_hminner, sizeof(sha1ctx.h.b32));
+       }
+
+       for (i = 0; i < klen; i++)
+               key[i] ^= (HMAC_IPAD_VAL ^ HMAC_OPAD_VAL);
+
+       if (algo == CRYPTO_MD5_HMAC) {
+               MD5Init(&md5ctx);
+               MD5Update(&md5ctx, key, klen);
+               MD5Update(&md5ctx, hmac_opad_buffer, MD5_HMAC_BLOCK_LEN - klen);
+               bcopy(md5ctx.md5_st8, ses->ses_hmouter, sizeof(md5ctx.md5_st8));
+       } else {
+               SHA1Init(&sha1ctx);
+               SHA1Update(&sha1ctx, key, klen);
+               SHA1Update(&sha1ctx, hmac_opad_buffer,
+                   SHA1_HMAC_BLOCK_LEN - klen);
+               bcopy(sha1ctx.h.b32, ses->ses_hmouter, sizeof(sha1ctx.h.b32));
+       }
+
+       for (i = 0; i < klen; i++)
+               key[i] ^= HMAC_OPAD_VAL;
+
+#if 0
+       /*
+        * this code prevents SHA working on a BE host,
+        * so it is obviously wrong.  I think the byte
+        * swap setup we do with the chip fixes this for us
+        */
+
+       /* PE is little-endian, insure proper byte order */
+       for (i = 0; i < N(ses->ses_hminner); i++) {
+               ses->ses_hminner[i] = htole32(ses->ses_hminner[i]);
+               ses->ses_hmouter[i] = htole32(ses->ses_hmouter[i]);
+       }
+#endif
+#else /* HMAC_HACK */
+       printk("safe: md5/sha not implemented\n");
+#endif /* HMAC_HACK */
+}
+#undef N
+
+/*
+ * Allocate a new 'session' and return an encoded session id.  'sidp'
+ * contains our registration id, and should contain an encoded session
+ * id on successful allocation.
+ */
+static int
+safe_newsession(device_t dev, u_int32_t *sidp, struct cryptoini *cri)
+{
+       struct safe_softc *sc = device_get_softc(dev);
+       struct cryptoini *c, *encini = NULL, *macini = NULL;
+       struct safe_session *ses = NULL;
+       int sesn;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (sidp == NULL || cri == NULL || sc == NULL)
+               return (EINVAL);
+
+       for (c = cri; c != NULL; c = c->cri_next) {
+               if (c->cri_alg == CRYPTO_MD5_HMAC ||
+                   c->cri_alg == CRYPTO_SHA1_HMAC ||
+                   c->cri_alg == CRYPTO_NULL_HMAC) {
+                       if (macini)
+                               return (EINVAL);
+                       macini = c;
+               } else if (c->cri_alg == CRYPTO_DES_CBC ||
+                   c->cri_alg == CRYPTO_3DES_CBC ||
+                   c->cri_alg == CRYPTO_AES_CBC ||
+                   c->cri_alg == CRYPTO_NULL_CBC) {
+                       if (encini)
+                               return (EINVAL);
+                       encini = c;
+               } else
+                       return (EINVAL);
+       }
+       if (encini == NULL && macini == NULL)
+               return (EINVAL);
+       if (encini) {                   /* validate key length */
+               switch (encini->cri_alg) {
+               case CRYPTO_DES_CBC:
+                       if (encini->cri_klen != 64)
+                               return (EINVAL);
+                       break;
+               case CRYPTO_3DES_CBC:
+                       if (encini->cri_klen != 192)
+                               return (EINVAL);
+                       break;
+               case CRYPTO_AES_CBC:
+                       if (encini->cri_klen != 128 &&
+                           encini->cri_klen != 192 &&
+                           encini->cri_klen != 256)
+                               return (EINVAL);
+                       break;
+               }
+       }
+
+       if (sc->sc_sessions == NULL) {
+               ses = sc->sc_sessions = (struct safe_session *)
+                       kmalloc(sizeof(struct safe_session), SLAB_ATOMIC);
+               if (ses == NULL)
+                       return (ENOMEM);
+               memset(ses, 0, sizeof(struct safe_session));
+               sesn = 0;
+               sc->sc_nsessions = 1;
+       } else {
+               for (sesn = 0; sesn < sc->sc_nsessions; sesn++) {
+                       if (sc->sc_sessions[sesn].ses_used == 0) {
+                               ses = &sc->sc_sessions[sesn];
+                               break;
+                       }
+               }
+
+               if (ses == NULL) {
+                       sesn = sc->sc_nsessions;
+                       ses = (struct safe_session *)
+                               kmalloc((sesn + 1) * sizeof(struct safe_session), SLAB_ATOMIC);
+                       if (ses == NULL)
+                               return (ENOMEM);
+                       memset(ses, 0, (sesn + 1) * sizeof(struct safe_session));
+                       bcopy(sc->sc_sessions, ses, sesn *
+                           sizeof(struct safe_session));
+                       bzero(sc->sc_sessions, sesn *
+                           sizeof(struct safe_session));
+                       kfree(sc->sc_sessions);
+                       sc->sc_sessions = ses;
+                       ses = &sc->sc_sessions[sesn];
+                       sc->sc_nsessions++;
+               }
+       }
+
+       bzero(ses, sizeof(struct safe_session));
+       ses->ses_used = 1;
+
+       if (encini) {
+               /* get an IV */
+               /* XXX may read fewer than requested */
+               read_random(ses->ses_iv, sizeof(ses->ses_iv));
+
+               ses->ses_klen = encini->cri_klen;
+               if (encini->cri_key != NULL)
+                       safe_setup_enckey(ses, encini->cri_key);
+       }
+
+       if (macini) {
+               ses->ses_mlen = macini->cri_mlen;
+               if (ses->ses_mlen == 0) {
+                       if (macini->cri_alg == CRYPTO_MD5_HMAC)
+                               ses->ses_mlen = MD5_HASH_LEN;
+                       else
+                               ses->ses_mlen = SHA1_HASH_LEN;
+               }
+
+               if (macini->cri_key != NULL) {
+                       safe_setup_mackey(ses, macini->cri_alg, macini->cri_key,
+                           macini->cri_klen / 8);
+               }
+       }
+
+       *sidp = SAFE_SID(device_get_unit(sc->sc_dev), sesn);
+       return (0);
+}
+
+/*
+ * Deallocate a session.
+ */
+static int
+safe_freesession(device_t dev, u_int64_t tid)
+{
+       struct safe_softc *sc = device_get_softc(dev);
+       int session, ret;
+       u_int32_t sid = ((u_int32_t) tid) & 0xffffffff;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (sc == NULL)
+               return (EINVAL);
+
+       session = SAFE_SESSION(sid);
+       if (session < sc->sc_nsessions) {
+               bzero(&sc->sc_sessions[session], sizeof(sc->sc_sessions[session]));
+               ret = 0;
+       } else
+               ret = EINVAL;
+       return (ret);
+}
+
+
+static int
+safe_process(device_t dev, struct cryptop *crp, int hint)
+{
+       struct safe_softc *sc = device_get_softc(dev);
+       int err = 0, i, nicealign, uniform;
+       struct cryptodesc *crd1, *crd2, *maccrd, *enccrd;
+       int bypass, oplen, ivsize;
+       caddr_t iv;
+       int16_t coffset;
+       struct safe_session *ses;
+       struct safe_ringentry *re;
+       struct safe_sarec *sa;
+       struct safe_pdesc *pd;
+       u_int32_t cmd0, cmd1, staterec;
+       unsigned long flags;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (crp == NULL || crp->crp_callback == NULL || sc == NULL) {
+               safestats.st_invalid++;
+               return (EINVAL);
+       }
+       if (SAFE_SESSION(crp->crp_sid) >= sc->sc_nsessions) {
+               safestats.st_badsession++;
+               return (EINVAL);
+       }
+
+       spin_lock_irqsave(&sc->sc_ringmtx, flags);
+       if (sc->sc_front == sc->sc_back && sc->sc_nqchip != 0) {
+               safestats.st_ringfull++;
+               sc->sc_needwakeup |= CRYPTO_SYMQ;
+               spin_unlock_irqrestore(&sc->sc_ringmtx, flags);
+               return (ERESTART);
+       }
+       re = sc->sc_front;
+
+       staterec = re->re_sa.sa_staterec;       /* save */
+       /* NB: zero everything but the PE descriptor */
+       bzero(&re->re_sa, sizeof(struct safe_ringentry) - sizeof(re->re_desc));
+       re->re_sa.sa_staterec = staterec;       /* restore */
+
+       re->re_crp = crp;
+       re->re_sesn = SAFE_SESSION(crp->crp_sid);
+
+       re->re_src.nsegs = 0;
+       re->re_dst.nsegs = 0;
+
+       if (crp->crp_flags & CRYPTO_F_SKBUF) {
+               re->re_src_skb = (struct sk_buff *)crp->crp_buf;
+               re->re_dst_skb = (struct sk_buff *)crp->crp_buf;
+       } else if (crp->crp_flags & CRYPTO_F_IOV) {
+               re->re_src_io = (struct uio *)crp->crp_buf;
+               re->re_dst_io = (struct uio *)crp->crp_buf;
+       } else {
+               safestats.st_badflags++;
+               err = EINVAL;
+               goto errout;    /* XXX we don't handle contiguous blocks! */
+       }
+
+       sa = &re->re_sa;
+       ses = &sc->sc_sessions[re->re_sesn];
+
+       crd1 = crp->crp_desc;
+       if (crd1 == NULL) {
+               safestats.st_nodesc++;
+               err = EINVAL;
+               goto errout;
+       }
+       crd2 = crd1->crd_next;
+
+       cmd0 = SAFE_SA_CMD0_BASIC;              /* basic group operation */
+       cmd1 = 0;
+       if (crd2 == NULL) {
+               if (crd1->crd_alg == CRYPTO_MD5_HMAC ||
+                   crd1->crd_alg == CRYPTO_SHA1_HMAC ||
+                   crd1->crd_alg == CRYPTO_NULL_HMAC) {
+                       maccrd = crd1;
+                       enccrd = NULL;
+                       cmd0 |= SAFE_SA_CMD0_OP_HASH;
+               } else if (crd1->crd_alg == CRYPTO_DES_CBC ||
+                   crd1->crd_alg == CRYPTO_3DES_CBC ||
+                   crd1->crd_alg == CRYPTO_AES_CBC ||
+                   crd1->crd_alg == CRYPTO_NULL_CBC) {
+                       maccrd = NULL;
+                       enccrd = crd1;
+                       cmd0 |= SAFE_SA_CMD0_OP_CRYPT;
+               } else {
+                       safestats.st_badalg++;
+                       err = EINVAL;
+                       goto errout;
+               }
+       } else {
+               if ((crd1->crd_alg == CRYPTO_MD5_HMAC ||
+                   crd1->crd_alg == CRYPTO_SHA1_HMAC ||
+                   crd1->crd_alg == CRYPTO_NULL_HMAC) &&
+                   (crd2->crd_alg == CRYPTO_DES_CBC ||
+                       crd2->crd_alg == CRYPTO_3DES_CBC ||
+                       crd2->crd_alg == CRYPTO_AES_CBC ||
+                       crd2->crd_alg == CRYPTO_NULL_CBC) &&
+                   ((crd2->crd_flags & CRD_F_ENCRYPT) == 0)) {
+                       maccrd = crd1;
+                       enccrd = crd2;
+               } else if ((crd1->crd_alg == CRYPTO_DES_CBC ||
+                   crd1->crd_alg == CRYPTO_3DES_CBC ||
+                   crd1->crd_alg == CRYPTO_AES_CBC ||
+                   crd1->crd_alg == CRYPTO_NULL_CBC) &&
+                   (crd2->crd_alg == CRYPTO_MD5_HMAC ||
+                       crd2->crd_alg == CRYPTO_SHA1_HMAC ||
+                       crd2->crd_alg == CRYPTO_NULL_HMAC) &&
+                   (crd1->crd_flags & CRD_F_ENCRYPT)) {
+                       enccrd = crd1;
+                       maccrd = crd2;
+               } else {
+                       safestats.st_badalg++;
+                       err = EINVAL;
+                       goto errout;
+               }
+               cmd0 |= SAFE_SA_CMD0_OP_BOTH;
+       }
+
+       if (enccrd) {
+               if (enccrd->crd_flags & CRD_F_KEY_EXPLICIT)
+                       safe_setup_enckey(ses, enccrd->crd_key);
+
+               if (enccrd->crd_alg == CRYPTO_DES_CBC) {
+                       cmd0 |= SAFE_SA_CMD0_DES;
+                       cmd1 |= SAFE_SA_CMD1_CBC;
+                       ivsize = 2*sizeof(u_int32_t);
+               } else if (enccrd->crd_alg == CRYPTO_3DES_CBC) {
+                       cmd0 |= SAFE_SA_CMD0_3DES;
+                       cmd1 |= SAFE_SA_CMD1_CBC;
+                       ivsize = 2*sizeof(u_int32_t);
+               } else if (enccrd->crd_alg == CRYPTO_AES_CBC) {
+                       cmd0 |= SAFE_SA_CMD0_AES;
+                       cmd1 |= SAFE_SA_CMD1_CBC;
+                       if (ses->ses_klen == 128)
+                            cmd1 |=  SAFE_SA_CMD1_AES128;
+                       else if (ses->ses_klen == 192)
+                            cmd1 |=  SAFE_SA_CMD1_AES192;
+                       else
+                            cmd1 |=  SAFE_SA_CMD1_AES256;
+                       ivsize = 4*sizeof(u_int32_t);
+               } else {
+                       cmd0 |= SAFE_SA_CMD0_CRYPT_NULL;
+                       ivsize = 0;
+               }
+
+               /*
+                * Setup encrypt/decrypt state.  When using basic ops
+                * we can't use an inline IV because hash/crypt offset
+                * must be from the end of the IV to the start of the
+                * crypt data and this leaves out the preceding header
+                * from the hash calculation.  Instead we place the IV
+                * in the state record and set the hash/crypt offset to
+                * copy both the header+IV.
+                */
+               if (enccrd->crd_flags & CRD_F_ENCRYPT) {
+                       cmd0 |= SAFE_SA_CMD0_OUTBOUND;
+
+                       if (enccrd->crd_flags & CRD_F_IV_EXPLICIT)
+                               iv = enccrd->crd_iv;
+                       else
+                               iv = (caddr_t) ses->ses_iv;
+                       if ((enccrd->crd_flags & CRD_F_IV_PRESENT) == 0) {
+                               crypto_copyback(crp->crp_flags, crp->crp_buf,
+                                   enccrd->crd_inject, ivsize, iv);
+                       }
+                       bcopy(iv, re->re_sastate.sa_saved_iv, ivsize);
+                       /* make iv LE */
+                       for (i = 0; i < ivsize/sizeof(re->re_sastate.sa_saved_iv[0]); i++)
+                               re->re_sastate.sa_saved_iv[i] =
+                                       cpu_to_le32(re->re_sastate.sa_saved_iv[i]);
+                       cmd0 |= SAFE_SA_CMD0_IVLD_STATE | SAFE_SA_CMD0_SAVEIV;
+                       re->re_flags |= SAFE_QFLAGS_COPYOUTIV;
+               } else {
+                       cmd0 |= SAFE_SA_CMD0_INBOUND;
+
+                       if (enccrd->crd_flags & CRD_F_IV_EXPLICIT) {
+                               bcopy(enccrd->crd_iv,
+                                       re->re_sastate.sa_saved_iv, ivsize);
+                       } else {
+                               crypto_copydata(crp->crp_flags, crp->crp_buf,
+                                   enccrd->crd_inject, ivsize,
+                                   (caddr_t)re->re_sastate.sa_saved_iv);
+                       }
+                       /* make iv LE */
+                       for (i = 0; i < ivsize/sizeof(re->re_sastate.sa_saved_iv[0]); i++)
+                               re->re_sastate.sa_saved_iv[i] =
+                                       cpu_to_le32(re->re_sastate.sa_saved_iv[i]);
+                       cmd0 |= SAFE_SA_CMD0_IVLD_STATE;
+               }
+               /*
+                * For basic encryption use the zero pad algorithm.
+                * This pads results to an 8-byte boundary and
+                * suppresses padding verification for inbound (i.e.
+                * decrypt) operations.
+                *
+                * NB: Not sure if the 8-byte pad boundary is a problem.
+                */
+               cmd0 |= SAFE_SA_CMD0_PAD_ZERO;
+
+               /* XXX assert key bufs have the same size */
+               bcopy(ses->ses_key, sa->sa_key, sizeof(sa->sa_key));
+       }
+
+       if (maccrd) {
+               if (maccrd->crd_flags & CRD_F_KEY_EXPLICIT) {
+                       safe_setup_mackey(ses, maccrd->crd_alg,
+                           maccrd->crd_key, maccrd->crd_klen / 8);
+               }
+
+               if (maccrd->crd_alg == CRYPTO_MD5_HMAC) {
+                       cmd0 |= SAFE_SA_CMD0_MD5;
+                       cmd1 |= SAFE_SA_CMD1_HMAC;      /* NB: enable HMAC */
+               } else if (maccrd->crd_alg == CRYPTO_SHA1_HMAC) {
+                       cmd0 |= SAFE_SA_CMD0_SHA1;
+                       cmd1 |= SAFE_SA_CMD1_HMAC;      /* NB: enable HMAC */
+               } else {
+                       cmd0 |= SAFE_SA_CMD0_HASH_NULL;
+               }
+               /*
+                * Digest data is loaded from the SA and the hash
+                * result is saved to the state block where we
+                * retrieve it for return to the caller.
+                */
+               /* XXX assert digest bufs have the same size */
+               bcopy(ses->ses_hminner, sa->sa_indigest,
+                       sizeof(sa->sa_indigest));
+               bcopy(ses->ses_hmouter, sa->sa_outdigest,
+                       sizeof(sa->sa_outdigest));
+
+               cmd0 |= SAFE_SA_CMD0_HSLD_SA | SAFE_SA_CMD0_SAVEHASH;
+               re->re_flags |= SAFE_QFLAGS_COPYOUTICV;
+       }
+
+       if (enccrd && maccrd) {
+               /*
+                * The offset from hash data to the start of
+                * crypt data is the difference in the skips.
+                */
+               bypass = maccrd->crd_skip;
+               coffset = enccrd->crd_skip - maccrd->crd_skip;
+               if (coffset < 0) {
+                       DPRINTF(("%s: hash does not precede crypt; "
+                               "mac skip %u enc skip %u\n",
+                               __func__, maccrd->crd_skip, enccrd->crd_skip));
+                       safestats.st_skipmismatch++;
+                       err = EINVAL;
+                       goto errout;
+               }
+               oplen = enccrd->crd_skip + enccrd->crd_len;
+               if (maccrd->crd_skip + maccrd->crd_len != oplen) {
+                       DPRINTF(("%s: hash amount %u != crypt amount %u\n",
+                               __func__, maccrd->crd_skip + maccrd->crd_len,
+                               oplen));
+                       safestats.st_lenmismatch++;
+                       err = EINVAL;
+                       goto errout;
+               }
+#ifdef SAFE_DEBUG
+               if (debug) {
+                       printf("mac: skip %d, len %d, inject %d\n",
+                           maccrd->crd_skip, maccrd->crd_len,
+                           maccrd->crd_inject);
+                       printf("enc: skip %d, len %d, inject %d\n",
+                           enccrd->crd_skip, enccrd->crd_len,
+                           enccrd->crd_inject);
+                       printf("bypass %d coffset %d oplen %d\n",
+                               bypass, coffset, oplen);
+               }
+#endif
+               if (coffset & 3) {      /* offset must be 32-bit aligned */
+                       DPRINTF(("%s: coffset %u misaligned\n",
+                               __func__, coffset));
+                       safestats.st_coffmisaligned++;
+                       err = EINVAL;
+                       goto errout;
+               }
+               coffset >>= 2;
+               if (coffset > 255) {    /* offset must be <256 dwords */
+                       DPRINTF(("%s: coffset %u too big\n",
+                               __func__, coffset));
+                       safestats.st_cofftoobig++;
+                       err = EINVAL;
+                       goto errout;
+               }
+               /*
+                * Tell the hardware to copy the header to the output.
+                * The header is defined as the data from the end of
+                * the bypass to the start of data to be encrypted. 
+                * Typically this is the inline IV.  Note that you need
+                * to do this even if src+dst are the same; it appears
+                * that w/o this bit the crypted data is written
+                * immediately after the bypass data.
+                */
+               cmd1 |= SAFE_SA_CMD1_HDRCOPY;
+               /*
+                * Disable IP header mutable bit handling.  This is
+                * needed to get correct HMAC calculations.
+                */
+               cmd1 |= SAFE_SA_CMD1_MUTABLE;
+       } else {
+               if (enccrd) {
+                       bypass = enccrd->crd_skip;
+                       oplen = bypass + enccrd->crd_len;
+               } else {
+                       bypass = maccrd->crd_skip;
+                       oplen = bypass + maccrd->crd_len;
+               }
+               coffset = 0;
+       }
+       /* XXX verify multiple of 4 when using s/g */
+       if (bypass > 96) {              /* bypass offset must be <= 96 bytes */
+               DPRINTF(("%s: bypass %u too big\n", __func__, bypass));
+               safestats.st_bypasstoobig++;
+               err = EINVAL;
+               goto errout;
+       }
+
+       if (crp->crp_flags & CRYPTO_F_SKBUF) {
+               if (pci_map_skb(sc, &re->re_src, re->re_src_skb)) {
+                       safestats.st_noload++;
+                       err = ENOMEM;
+                       goto errout;
+               }
+       } else if (crp->crp_flags & CRYPTO_F_IOV) {
+               if (pci_map_uio(sc, &re->re_src, re->re_src_io)) {
+                       safestats.st_noload++;
+                       err = ENOMEM;
+                       goto errout;
+               }
+       }
+       nicealign = safe_dmamap_aligned(sc, &re->re_src);
+       uniform = safe_dmamap_uniform(sc, &re->re_src);
+
+       DPRINTF(("src nicealign %u uniform %u nsegs %u\n",
+               nicealign, uniform, re->re_src.nsegs));
+       if (re->re_src.nsegs > 1) {
+               re->re_desc.d_src = sc->sc_spalloc.dma_paddr +
+                       ((caddr_t) sc->sc_spfree - (caddr_t) sc->sc_spring);
+               for (i = 0; i < re->re_src_nsegs; i++) {
+                       /* NB: no need to check if there's space */
+                       pd = sc->sc_spfree;
+                       if (++(sc->sc_spfree) == sc->sc_springtop)
+                               sc->sc_spfree = sc->sc_spring;
+
+                       KASSERT((pd->pd_flags&3) == 0 ||
+                               (pd->pd_flags&3) == SAFE_PD_DONE,
+                               ("bogus source particle descriptor; flags %x",
+                               pd->pd_flags));
+                       pd->pd_addr = re->re_src_segs[i].ds_addr;
+                       pd->pd_size = re->re_src_segs[i].ds_len;
+                       pd->pd_flags = SAFE_PD_READY;
+               }
+               cmd0 |= SAFE_SA_CMD0_IGATHER;
+       } else {
+               /*
+                * No need for gather, reference the operand directly.
+                */
+               re->re_desc.d_src = re->re_src_segs[0].ds_addr;
+       }
+
+       if (enccrd == NULL && maccrd != NULL) {
+               /*
+                * Hash op; no destination needed.
+                */
+       } else {
+               if (crp->crp_flags & (CRYPTO_F_IOV|CRYPTO_F_SKBUF)) {
+                       if (!nicealign) {
+                               safestats.st_iovmisaligned++;
+                               err = EINVAL;
+                               goto errout;
+                       }
+                       if (uniform != 1) {
+                               device_printf(sc->sc_dev, "!uniform source\n");
+                               if (!uniform) {
+                                       /*
+                                        * There's no way to handle the DMA
+                                        * requirements with this uio.  We
+                                        * could create a separate DMA area for
+                                        * the result and then copy it back,
+                                        * but for now we just bail and return
+                                        * an error.  Note that uio requests
+                                        * > SAFE_MAX_DSIZE are handled because
+                                        * the DMA map and segment list for the
+                                        * destination wil result in a
+                                        * destination particle list that does
+                                        * the necessary scatter DMA.
+                                        */ 
+                                       safestats.st_iovnotuniform++;
+                                       err = EINVAL;
+                                       goto errout;
+                               }
+                       } else
+                               re->re_dst = re->re_src;
+               } else {
+                       safestats.st_badflags++;
+                       err = EINVAL;
+                       goto errout;
+               }
+
+               if (re->re_dst.nsegs > 1) {
+                       re->re_desc.d_dst = sc->sc_dpalloc.dma_paddr +
+                           ((caddr_t) sc->sc_dpfree - (caddr_t) sc->sc_dpring);
+                       for (i = 0; i < re->re_dst_nsegs; i++) {
+                               pd = sc->sc_dpfree;
+                               KASSERT((pd->pd_flags&3) == 0 ||
+                                       (pd->pd_flags&3) == SAFE_PD_DONE,
+                                       ("bogus dest particle descriptor; flags %x",
+                                               pd->pd_flags));
+                               if (++(sc->sc_dpfree) == sc->sc_dpringtop)
+                                       sc->sc_dpfree = sc->sc_dpring;
+                               pd->pd_addr = re->re_dst_segs[i].ds_addr;
+                               pd->pd_flags = SAFE_PD_READY;
+                       }
+                       cmd0 |= SAFE_SA_CMD0_OSCATTER;
+               } else {
+                       /*
+                        * No need for scatter, reference the operand directly.
+                        */
+                       re->re_desc.d_dst = re->re_dst_segs[0].ds_addr;
+               }
+       }
+
+       /*
+        * All done with setup; fillin the SA command words
+        * and the packet engine descriptor.  The operation
+        * is now ready for submission to the hardware.
+        */
+       sa->sa_cmd0 = cmd0 | SAFE_SA_CMD0_IPCI | SAFE_SA_CMD0_OPCI;
+       sa->sa_cmd1 = cmd1
+                   | (coffset << SAFE_SA_CMD1_OFFSET_S)
+                   | SAFE_SA_CMD1_SAREV1       /* Rev 1 SA data structure */
+                   | SAFE_SA_CMD1_SRPCI
+                   ;
+       /*
+        * NB: the order of writes is important here.  In case the
+        * chip is scanning the ring because of an outstanding request
+        * it might nab this one too.  In that case we need to make
+        * sure the setup is complete before we write the length
+        * field of the descriptor as it signals the descriptor is
+        * ready for processing.
+        */
+       re->re_desc.d_csr = SAFE_PE_CSR_READY | SAFE_PE_CSR_SAPCI;
+       if (maccrd)
+               re->re_desc.d_csr |= SAFE_PE_CSR_LOADSA | SAFE_PE_CSR_HASHFINAL;
+       wmb();
+       re->re_desc.d_len = oplen
+                         | SAFE_PE_LEN_READY
+                         | (bypass << SAFE_PE_LEN_BYPASS_S)
+                         ;
+
+       safestats.st_ipackets++;
+       safestats.st_ibytes += oplen;
+
+       if (++(sc->sc_front) == sc->sc_ringtop)
+               sc->sc_front = sc->sc_ring;
+
+       /* XXX honor batching */
+       safe_feed(sc, re);
+       spin_unlock_irqrestore(&sc->sc_ringmtx, flags);
+       return (0);
+
+errout:
+       if (re->re_src.map != re->re_dst.map)
+               pci_unmap_operand(sc, &re->re_dst);
+       if (re->re_src.map)
+               pci_unmap_operand(sc, &re->re_src);
+       spin_unlock_irqrestore(&sc->sc_ringmtx, flags);
+       if (err != ERESTART) {
+               crp->crp_etype = err;
+               crypto_done(crp);
+       } else {
+               sc->sc_needwakeup |= CRYPTO_SYMQ;
+       }
+       return (err);
+}
+
+static void
+safe_callback(struct safe_softc *sc, struct safe_ringentry *re)
+{
+       struct cryptop *crp = (struct cryptop *)re->re_crp;
+       struct cryptodesc *crd;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       safestats.st_opackets++;
+       safestats.st_obytes += re->re_dst.mapsize;
+
+       if (re->re_desc.d_csr & SAFE_PE_CSR_STATUS) {
+               device_printf(sc->sc_dev, "csr 0x%x cmd0 0x%x cmd1 0x%x\n",
+                       re->re_desc.d_csr,
+                       re->re_sa.sa_cmd0, re->re_sa.sa_cmd1);
+               safestats.st_peoperr++;
+               crp->crp_etype = EIO;           /* something more meaningful? */
+       }
+
+       if (re->re_dst.map != NULL && re->re_dst.map != re->re_src.map)
+               pci_unmap_operand(sc, &re->re_dst);
+       pci_unmap_operand(sc, &re->re_src);
+
+       /* 
+        * If result was written to a differet mbuf chain, swap
+        * it in as the return value and reclaim the original.
+        */
+       if ((crp->crp_flags & CRYPTO_F_SKBUF) && re->re_src_skb != re->re_dst_skb) {
+               device_printf(sc->sc_dev, "no CRYPTO_F_SKBUF swapping support\n");
+               /* kfree_skb(skb) */
+               /* crp->crp_buf = (caddr_t)re->re_dst_skb */
+               return;
+       }
+
+       if (re->re_flags & SAFE_QFLAGS_COPYOUTIV) {
+               /* copy out IV for future use */
+               for (crd = crp->crp_desc; crd; crd = crd->crd_next) {
+                       int i;
+                       int ivsize;
+
+                       if (crd->crd_alg == CRYPTO_DES_CBC ||
+                           crd->crd_alg == CRYPTO_3DES_CBC) {
+                               ivsize = 2*sizeof(u_int32_t);
+                       } else if (crd->crd_alg == CRYPTO_AES_CBC) {
+                               ivsize = 4*sizeof(u_int32_t);
+                       } else
+                               continue;
+                       crypto_copydata(crp->crp_flags, crp->crp_buf,
+                           crd->crd_skip + crd->crd_len - ivsize, ivsize,
+                           (caddr_t)sc->sc_sessions[re->re_sesn].ses_iv);
+                       for (i = 0;
+                                       i < ivsize/sizeof(sc->sc_sessions[re->re_sesn].ses_iv[0]);
+                                       i++)
+                               sc->sc_sessions[re->re_sesn].ses_iv[i] =
+                                       cpu_to_le32(sc->sc_sessions[re->re_sesn].ses_iv[i]);
+                       break;
+               }
+       }
+
+       if (re->re_flags & SAFE_QFLAGS_COPYOUTICV) {
+               /* copy out ICV result */
+               for (crd = crp->crp_desc; crd; crd = crd->crd_next) {
+                       if (!(crd->crd_alg == CRYPTO_MD5_HMAC ||
+                           crd->crd_alg == CRYPTO_SHA1_HMAC ||
+                           crd->crd_alg == CRYPTO_NULL_HMAC))
+                               continue;
+                       if (crd->crd_alg == CRYPTO_SHA1_HMAC) {
+                               /*
+                                * SHA-1 ICV's are byte-swapped; fix 'em up
+                                * before copy them to their destination.
+                                */
+                               re->re_sastate.sa_saved_indigest[0] =
+                                       cpu_to_be32(re->re_sastate.sa_saved_indigest[0]);
+                               re->re_sastate.sa_saved_indigest[1] = 
+                                       cpu_to_be32(re->re_sastate.sa_saved_indigest[1]);
+                               re->re_sastate.sa_saved_indigest[2] =
+                                       cpu_to_be32(re->re_sastate.sa_saved_indigest[2]);
+                       } else {
+                               re->re_sastate.sa_saved_indigest[0] =
+                                       cpu_to_le32(re->re_sastate.sa_saved_indigest[0]);
+                               re->re_sastate.sa_saved_indigest[1] = 
+                                       cpu_to_le32(re->re_sastate.sa_saved_indigest[1]);
+                               re->re_sastate.sa_saved_indigest[2] =
+                                       cpu_to_le32(re->re_sastate.sa_saved_indigest[2]);
+                       }
+                       crypto_copyback(crp->crp_flags, crp->crp_buf,
+                           crd->crd_inject,
+                           sc->sc_sessions[re->re_sesn].ses_mlen,
+                           (caddr_t)re->re_sastate.sa_saved_indigest);
+                       break;
+               }
+       }
+       crypto_done(crp);
+}
+
+
+#if defined(CONFIG_OCF_RANDOMHARVEST) && !defined(SAFE_NO_RNG)
+#define        SAFE_RNG_MAXWAIT        1000
+
+static void
+safe_rng_init(struct safe_softc *sc)
+{
+       u_int32_t w, v;
+       int i;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       WRITE_REG(sc, SAFE_RNG_CTRL, 0);
+       /* use default value according to the manual */
+       WRITE_REG(sc, SAFE_RNG_CNFG, 0x834);    /* magic from SafeNet */
+       WRITE_REG(sc, SAFE_RNG_ALM_CNT, 0);
+
+       /*
+        * There is a bug in rev 1.0 of the 1140 that when the RNG
+        * is brought out of reset the ready status flag does not
+        * work until the RNG has finished its internal initialization.
+        *
+        * So in order to determine the device is through its
+        * initialization we must read the data register, using the
+        * status reg in the read in case it is initialized.  Then read
+        * the data register until it changes from the first read.
+        * Once it changes read the data register until it changes
+        * again.  At this time the RNG is considered initialized. 
+        * This could take between 750ms - 1000ms in time.
+        */
+       i = 0;
+       w = READ_REG(sc, SAFE_RNG_OUT);
+       do {
+               v = READ_REG(sc, SAFE_RNG_OUT);
+               if (v != w) {
+                       w = v;
+                       break;
+               }
+               DELAY(10);
+       } while (++i < SAFE_RNG_MAXWAIT);
+
+       /* Wait Until data changes again */
+       i = 0;
+       do {
+               v = READ_REG(sc, SAFE_RNG_OUT);
+               if (v != w)
+                       break;
+               DELAY(10);
+       } while (++i < SAFE_RNG_MAXWAIT);
+}
+
+static __inline void
+safe_rng_disable_short_cycle(struct safe_softc *sc)
+{
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       WRITE_REG(sc, SAFE_RNG_CTRL,
+               READ_REG(sc, SAFE_RNG_CTRL) &~ SAFE_RNG_CTRL_SHORTEN);
+}
+
+static __inline void
+safe_rng_enable_short_cycle(struct safe_softc *sc)
+{
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       WRITE_REG(sc, SAFE_RNG_CTRL, 
+               READ_REG(sc, SAFE_RNG_CTRL) | SAFE_RNG_CTRL_SHORTEN);
+}
+
+static __inline u_int32_t
+safe_rng_read(struct safe_softc *sc)
+{
+       int i;
+
+       i = 0;
+       while (READ_REG(sc, SAFE_RNG_STAT) != 0 && ++i < SAFE_RNG_MAXWAIT)
+               ;
+       return READ_REG(sc, SAFE_RNG_OUT);
+}
+
+static int
+safe_read_random(void *arg, u_int32_t *buf, int maxwords)
+{
+       struct safe_softc *sc = (struct safe_softc *) arg;
+       int i, rc;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+       
+       safestats.st_rng++;
+       /*
+        * Fetch the next block of data.
+        */
+       if (maxwords > safe_rngbufsize)
+               maxwords = safe_rngbufsize;
+       if (maxwords > SAFE_RNG_MAXBUFSIZ)
+               maxwords = SAFE_RNG_MAXBUFSIZ;
+retry:
+       /* read as much as we can */
+       for (rc = 0; rc < maxwords; rc++) {
+               if (READ_REG(sc, SAFE_RNG_STAT) != 0)
+                       break;
+               buf[rc] = READ_REG(sc, SAFE_RNG_OUT);
+       }
+       if (rc == 0)
+               return 0;
+       /*
+        * Check the comparator alarm count and reset the h/w if
+        * it exceeds our threshold.  This guards against the
+        * hardware oscillators resonating with external signals.
+        */
+       if (READ_REG(sc, SAFE_RNG_ALM_CNT) > safe_rngmaxalarm) {
+               u_int32_t freq_inc, w;
+
+               DPRINTF(("%s: alarm count %u exceeds threshold %u\n", __func__,
+                       (unsigned)READ_REG(sc, SAFE_RNG_ALM_CNT), safe_rngmaxalarm));
+               safestats.st_rngalarm++;
+               safe_rng_enable_short_cycle(sc);
+               freq_inc = 18;
+               for (i = 0; i < 64; i++) {
+                       w = READ_REG(sc, SAFE_RNG_CNFG);
+                       freq_inc = ((w + freq_inc) & 0x3fL);
+                       w = ((w & ~0x3fL) | freq_inc);
+                       WRITE_REG(sc, SAFE_RNG_CNFG, w);
+
+                       WRITE_REG(sc, SAFE_RNG_ALM_CNT, 0);
+
+                       (void) safe_rng_read(sc);
+                       DELAY(25);
+
+                       if (READ_REG(sc, SAFE_RNG_ALM_CNT) == 0) {
+                               safe_rng_disable_short_cycle(sc);
+                               goto retry;
+                       }
+                       freq_inc = 1;
+               }
+               safe_rng_disable_short_cycle(sc);
+       } else
+               WRITE_REG(sc, SAFE_RNG_ALM_CNT, 0);
+
+       return(rc);
+}
+#endif /* defined(CONFIG_OCF_RANDOMHARVEST) && !defined(SAFE_NO_RNG) */
+
+
+/*
+ * Resets the board.  Values in the regesters are left as is
+ * from the reset (i.e. initial values are assigned elsewhere).
+ */
+static void
+safe_reset_board(struct safe_softc *sc)
+{
+       u_int32_t v;
+       /*
+        * Reset the device.  The manual says no delay
+        * is needed between marking and clearing reset.
+        */
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       v = READ_REG(sc, SAFE_PE_DMACFG) &~
+               (SAFE_PE_DMACFG_PERESET | SAFE_PE_DMACFG_PDRRESET |
+                SAFE_PE_DMACFG_SGRESET);
+       WRITE_REG(sc, SAFE_PE_DMACFG, v
+                                   | SAFE_PE_DMACFG_PERESET
+                                   | SAFE_PE_DMACFG_PDRRESET
+                                   | SAFE_PE_DMACFG_SGRESET);
+       WRITE_REG(sc, SAFE_PE_DMACFG, v);
+}
+
+/*
+ * Initialize registers we need to touch only once.
+ */
+static void
+safe_init_board(struct safe_softc *sc)
+{
+       u_int32_t v, dwords;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       v = READ_REG(sc, SAFE_PE_DMACFG);
+       v &=~ (   SAFE_PE_DMACFG_PEMODE
+                       | SAFE_PE_DMACFG_FSENA          /* failsafe enable */
+                       | SAFE_PE_DMACFG_GPRPCI         /* gather ring on PCI */
+                       | SAFE_PE_DMACFG_SPRPCI         /* scatter ring on PCI */
+                       | SAFE_PE_DMACFG_ESDESC         /* endian-swap descriptors */
+                       | SAFE_PE_DMACFG_ESPDESC        /* endian-swap part. desc's */
+                       | SAFE_PE_DMACFG_ESSA           /* endian-swap SA's */
+                       | SAFE_PE_DMACFG_ESPACKET       /* swap the packet data */
+                 );
+       v |= SAFE_PE_DMACFG_FSENA               /* failsafe enable */
+         |  SAFE_PE_DMACFG_GPRPCI              /* gather ring on PCI */
+         |  SAFE_PE_DMACFG_SPRPCI              /* scatter ring on PCI */
+         |  SAFE_PE_DMACFG_ESDESC              /* endian-swap descriptors */
+         |  SAFE_PE_DMACFG_ESPDESC             /* endian-swap part. desc's */
+         |  SAFE_PE_DMACFG_ESSA                /* endian-swap SA's */
+#if 0
+         |  SAFE_PE_DMACFG_ESPACKET    /* swap the packet data */
+#endif
+         ;
+       WRITE_REG(sc, SAFE_PE_DMACFG, v);
+
+#ifdef __BIG_ENDIAN
+       /* tell the safenet that we are 4321 and not 1234 */
+       WRITE_REG(sc, SAFE_ENDIAN, 0xe4e41b1b);
+#endif
+
+       if (sc->sc_chiprev == SAFE_REV(1,0)) {
+               /*
+                * Avoid large PCI DMA transfers.  Rev 1.0 has a bug where
+                * "target mode transfers" done while the chip is DMA'ing
+                * >1020 bytes cause the hardware to lockup.  To avoid this
+                * we reduce the max PCI transfer size and use small source
+                * particle descriptors (<= 256 bytes).
+                */
+               WRITE_REG(sc, SAFE_DMA_CFG, 256);
+               device_printf(sc->sc_dev,
+                       "Reduce max DMA size to %u words for rev %u.%u WAR\n",
+                       (unsigned) ((READ_REG(sc, SAFE_DMA_CFG)>>2) & 0xff),
+                       (unsigned) SAFE_REV_MAJ(sc->sc_chiprev),
+                       (unsigned) SAFE_REV_MIN(sc->sc_chiprev));
+               sc->sc_max_dsize = 256;
+       } else {
+               sc->sc_max_dsize = SAFE_MAX_DSIZE;
+       }
+
+       /* NB: operands+results are overlaid */
+       WRITE_REG(sc, SAFE_PE_PDRBASE, sc->sc_ringalloc.dma_paddr);
+       WRITE_REG(sc, SAFE_PE_RDRBASE, sc->sc_ringalloc.dma_paddr);
+       /*
+        * Configure ring entry size and number of items in the ring.
+        */
+       KASSERT((sizeof(struct safe_ringentry) % sizeof(u_int32_t)) == 0,
+               ("PE ring entry not 32-bit aligned!"));
+       dwords = sizeof(struct safe_ringentry) / sizeof(u_int32_t);
+       WRITE_REG(sc, SAFE_PE_RINGCFG,
+               (dwords << SAFE_PE_RINGCFG_OFFSET_S) | SAFE_MAX_NQUEUE);
+       WRITE_REG(sc, SAFE_PE_RINGPOLL, 0);     /* disable polling */
+
+       WRITE_REG(sc, SAFE_PE_GRNGBASE, sc->sc_spalloc.dma_paddr);
+       WRITE_REG(sc, SAFE_PE_SRNGBASE, sc->sc_dpalloc.dma_paddr);
+       WRITE_REG(sc, SAFE_PE_PARTSIZE,
+               (SAFE_TOTAL_DPART<<16) | SAFE_TOTAL_SPART);
+       /*
+        * NB: destination particles are fixed size.  We use
+        *     an mbuf cluster and require all results go to
+        *     clusters or smaller.
+        */
+       WRITE_REG(sc, SAFE_PE_PARTCFG, sc->sc_max_dsize);
+
+       /* it's now safe to enable PE mode, do it */
+       WRITE_REG(sc, SAFE_PE_DMACFG, v | SAFE_PE_DMACFG_PEMODE);
+
+       /*
+        * Configure hardware to use level-triggered interrupts and
+        * to interrupt after each descriptor is processed.
+        */
+       WRITE_REG(sc, SAFE_HI_CFG, SAFE_HI_CFG_LEVEL);
+       WRITE_REG(sc, SAFE_HI_CLR, 0xffffffff);
+       WRITE_REG(sc, SAFE_HI_DESC_CNT, 1);
+       WRITE_REG(sc, SAFE_HI_MASK, SAFE_INT_PE_DDONE | SAFE_INT_PE_ERROR);
+}
+
+
+/*
+ * Clean up after a chip crash.
+ * It is assumed that the caller in splimp()
+ */
+static void
+safe_cleanchip(struct safe_softc *sc)
+{
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (sc->sc_nqchip != 0) {
+               struct safe_ringentry *re = sc->sc_back;
+
+               while (re != sc->sc_front) {
+                       if (re->re_desc.d_csr != 0)
+                               safe_free_entry(sc, re);
+                       if (++re == sc->sc_ringtop)
+                               re = sc->sc_ring;
+               }
+               sc->sc_back = re;
+               sc->sc_nqchip = 0;
+       }
+}
+
+/*
+ * free a safe_q
+ * It is assumed that the caller is within splimp().
+ */
+static int
+safe_free_entry(struct safe_softc *sc, struct safe_ringentry *re)
+{
+       struct cryptop *crp;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       /*
+        * Free header MCR
+        */
+       if ((re->re_dst_skb != NULL) && (re->re_src_skb != re->re_dst_skb))
+#ifdef NOTYET
+               m_freem(re->re_dst_m);
+#else
+               printk("%s,%d: SKB not supported\n", __FILE__, __LINE__);
+#endif
+
+       crp = (struct cryptop *)re->re_crp;
+       
+       re->re_desc.d_csr = 0;
+       
+       crp->crp_etype = EFAULT;
+       crypto_done(crp);
+       return(0);
+}
+
+/*
+ * Routine to reset the chip and clean up.
+ * It is assumed that the caller is in splimp()
+ */
+static void
+safe_totalreset(struct safe_softc *sc)
+{
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       safe_reset_board(sc);
+       safe_init_board(sc);
+       safe_cleanchip(sc);
+}
+
+/*
+ * Is the operand suitable aligned for direct DMA.  Each
+ * segment must be aligned on a 32-bit boundary and all
+ * but the last segment must be a multiple of 4 bytes.
+ */
+static int
+safe_dmamap_aligned(struct safe_softc *sc, const struct safe_operand *op)
+{
+       int i;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       for (i = 0; i < op->nsegs; i++) {
+               if (op->segs[i].ds_addr & 3)
+                       return (0);
+               if (i != (op->nsegs - 1) && (op->segs[i].ds_len & 3))
+                       return (0);
+       }
+       return (1);
+}
+
+/*
+ * Is the operand suitable for direct DMA as the destination
+ * of an operation.  The hardware requires that each ``particle''
+ * but the last in an operation result have the same size.  We
+ * fix that size at SAFE_MAX_DSIZE bytes.  This routine returns
+ * 0 if some segment is not a multiple of of this size, 1 if all
+ * segments are exactly this size, or 2 if segments are at worst
+ * a multple of this size.
+ */
+static int
+safe_dmamap_uniform(struct safe_softc *sc, const struct safe_operand *op)
+{
+       int result = 1;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (op->nsegs > 0) {
+               int i;
+
+               for (i = 0; i < op->nsegs-1; i++) {
+                       if (op->segs[i].ds_len % sc->sc_max_dsize)
+                               return (0);
+                       if (op->segs[i].ds_len != sc->sc_max_dsize)
+                               result = 2;
+               }
+       }
+       return (result);
+}
+
+static int
+safe_kprocess(device_t dev, struct cryptkop *krp, int hint)
+{
+       struct safe_softc *sc = device_get_softc(dev);
+       struct safe_pkq *q;
+       unsigned long flags;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (sc == NULL) {
+               krp->krp_status = EINVAL;
+               goto err;
+       }
+
+       if (krp->krp_op != CRK_MOD_EXP) {
+               krp->krp_status = EOPNOTSUPP;
+               goto err;
+       }
+
+       q = (struct safe_pkq *) kmalloc(sizeof(*q), GFP_KERNEL);
+       if (q == NULL) {
+               krp->krp_status = ENOMEM;
+               goto err;
+       }
+       memset(q, 0, sizeof(*q));
+       q->pkq_krp = krp;
+       INIT_LIST_HEAD(&q->pkq_list);
+
+       spin_lock_irqsave(&sc->sc_pkmtx, flags);
+       list_add_tail(&q->pkq_list, &sc->sc_pkq);
+       safe_kfeed(sc);
+       spin_unlock_irqrestore(&sc->sc_pkmtx, flags);
+       return (0);
+
+err:
+       crypto_kdone(krp);
+       return (0);
+}
+
+#define        SAFE_CRK_PARAM_BASE     0
+#define        SAFE_CRK_PARAM_EXP      1
+#define        SAFE_CRK_PARAM_MOD      2
+
+static int
+safe_kstart(struct safe_softc *sc)
+{
+       struct cryptkop *krp = sc->sc_pkq_cur->pkq_krp;
+       int exp_bits, mod_bits, base_bits;
+       u_int32_t op, a_off, b_off, c_off, d_off;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (krp->krp_iparams < 3 || krp->krp_oparams != 1) {
+               krp->krp_status = EINVAL;
+               return (1);
+       }
+
+       base_bits = safe_ksigbits(sc, &krp->krp_param[SAFE_CRK_PARAM_BASE]);
+       if (base_bits > 2048)
+               goto too_big;
+       if (base_bits <= 0)             /* 5. base not zero */
+               goto too_small;
+
+       exp_bits = safe_ksigbits(sc, &krp->krp_param[SAFE_CRK_PARAM_EXP]);
+       if (exp_bits > 2048)
+               goto too_big;
+       if (exp_bits <= 0)              /* 1. exponent word length > 0 */
+               goto too_small;         /* 4. exponent not zero */
+
+       mod_bits = safe_ksigbits(sc, &krp->krp_param[SAFE_CRK_PARAM_MOD]);
+       if (mod_bits > 2048)
+               goto too_big;
+       if (mod_bits <= 32)             /* 2. modulus word length > 1 */
+               goto too_small;         /* 8. MSW of modulus != zero */
+       if (mod_bits < exp_bits)        /* 3 modulus len >= exponent len */
+               goto too_small;
+       if ((krp->krp_param[SAFE_CRK_PARAM_MOD].crp_p[0] & 1) == 0)
+               goto bad_domain;        /* 6. modulus is odd */
+       if (mod_bits > krp->krp_param[krp->krp_iparams].crp_nbits)
+               goto too_small;         /* make sure result will fit */
+
+       /* 7. modulus > base */
+       if (mod_bits < base_bits)
+               goto too_small;
+       if (mod_bits == base_bits) {
+               u_int8_t *basep, *modp;
+               int i;
+
+               basep = krp->krp_param[SAFE_CRK_PARAM_BASE].crp_p +
+                   ((base_bits + 7) / 8) - 1;
+               modp = krp->krp_param[SAFE_CRK_PARAM_MOD].crp_p +
+                   ((mod_bits + 7) / 8) - 1;
+               
+               for (i = 0; i < (mod_bits + 7) / 8; i++, basep--, modp--) {
+                       if (*modp < *basep)
+                               goto too_small;
+                       if (*modp > *basep)
+                               break;
+               }
+       }
+
+       /* And on the 9th step, he rested. */
+
+       WRITE_REG(sc, SAFE_PK_A_LEN, (exp_bits + 31) / 32);
+       WRITE_REG(sc, SAFE_PK_B_LEN, (mod_bits + 31) / 32);
+       if (mod_bits > 1024) {
+               op = SAFE_PK_FUNC_EXP4;
+               a_off = 0x000;
+               b_off = 0x100;
+               c_off = 0x200;
+               d_off = 0x300;
+       } else {
+               op = SAFE_PK_FUNC_EXP16;
+               a_off = 0x000;
+               b_off = 0x080;
+               c_off = 0x100;
+               d_off = 0x180;
+       }
+       sc->sc_pk_reslen = b_off - a_off;
+       sc->sc_pk_resoff = d_off;
+
+       /* A is exponent, B is modulus, C is base, D is result */
+       safe_kload_reg(sc, a_off, b_off - a_off,
+           &krp->krp_param[SAFE_CRK_PARAM_EXP]);
+       WRITE_REG(sc, SAFE_PK_A_ADDR, a_off >> 2);
+       safe_kload_reg(sc, b_off, b_off - a_off,
+           &krp->krp_param[SAFE_CRK_PARAM_MOD]);
+       WRITE_REG(sc, SAFE_PK_B_ADDR, b_off >> 2);
+       safe_kload_reg(sc, c_off, b_off - a_off,
+           &krp->krp_param[SAFE_CRK_PARAM_BASE]);
+       WRITE_REG(sc, SAFE_PK_C_ADDR, c_off >> 2);
+       WRITE_REG(sc, SAFE_PK_D_ADDR, d_off >> 2);
+
+       WRITE_REG(sc, SAFE_PK_FUNC, op | SAFE_PK_FUNC_RUN);
+
+       return (0);
+
+too_big:
+       krp->krp_status = E2BIG;
+       return (1);
+too_small:
+       krp->krp_status = ERANGE;
+       return (1);
+bad_domain:
+       krp->krp_status = EDOM;
+       return (1);
+}
+
+static int
+safe_ksigbits(struct safe_softc *sc, struct crparam *cr)
+{
+       u_int plen = (cr->crp_nbits + 7) / 8;
+       int i, sig = plen * 8;
+       u_int8_t c, *p = cr->crp_p;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       for (i = plen - 1; i >= 0; i--) {
+               c = p[i];
+               if (c != 0) {
+                       while ((c & 0x80) == 0) {
+                               sig--;
+                               c <<= 1;
+                       }
+                       break;
+               }
+               sig -= 8;
+       }
+       return (sig);
+}
+
+static void
+safe_kfeed(struct safe_softc *sc)
+{
+       struct safe_pkq *q, *tmp;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (list_empty(&sc->sc_pkq) && sc->sc_pkq_cur == NULL)
+               return;
+       if (sc->sc_pkq_cur != NULL)
+               return;
+       list_for_each_entry_safe(q, tmp, &sc->sc_pkq, pkq_list) {
+               sc->sc_pkq_cur = q;
+               list_del(&q->pkq_list);
+               if (safe_kstart(sc) != 0) {
+                       crypto_kdone(q->pkq_krp);
+                       kfree(q);
+                       sc->sc_pkq_cur = NULL;
+               } else {
+                       /* op started, start polling */
+                       mod_timer(&sc->sc_pkto, jiffies + 1);
+                       break;
+               }
+       }
+}
+
+static void
+safe_kpoll(unsigned long arg)
+{
+       struct safe_softc *sc = NULL;
+       struct safe_pkq *q;
+       struct crparam *res;
+       int i;
+       u_int32_t buf[64];
+       unsigned long flags;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (arg >= SAFE_MAX_CHIPS)
+               return;
+       sc = safe_chip_idx[arg];
+       if (!sc) {
+               DPRINTF(("%s() - bad callback\n", __FUNCTION__));
+               return;
+       }
+
+       spin_lock_irqsave(&sc->sc_pkmtx, flags);
+       if (sc->sc_pkq_cur == NULL)
+               goto out;
+       if (READ_REG(sc, SAFE_PK_FUNC) & SAFE_PK_FUNC_RUN) {
+               /* still running, check back later */
+               mod_timer(&sc->sc_pkto, jiffies + 1);
+               goto out;
+       }
+
+       q = sc->sc_pkq_cur;
+       res = &q->pkq_krp->krp_param[q->pkq_krp->krp_iparams];
+       bzero(buf, sizeof(buf));
+       bzero(res->crp_p, (res->crp_nbits + 7) / 8);
+       for (i = 0; i < sc->sc_pk_reslen >> 2; i++)
+               buf[i] = le32_to_cpu(READ_REG(sc, SAFE_PK_RAM_START +
+                   sc->sc_pk_resoff + (i << 2)));
+       bcopy(buf, res->crp_p, (res->crp_nbits + 7) / 8);
+       /*
+        * reduce the bits that need copying if possible
+        */
+       res->crp_nbits = min(res->crp_nbits,sc->sc_pk_reslen * 8);
+       res->crp_nbits = safe_ksigbits(sc, res);
+
+       for (i = SAFE_PK_RAM_START; i < SAFE_PK_RAM_END; i += 4)
+               WRITE_REG(sc, i, 0);
+
+       crypto_kdone(q->pkq_krp);
+       kfree(q);
+       sc->sc_pkq_cur = NULL;
+
+       safe_kfeed(sc);
+out:
+       spin_unlock_irqrestore(&sc->sc_pkmtx, flags);
+}
+
+static void
+safe_kload_reg(struct safe_softc *sc, u_int32_t off, u_int32_t len,
+    struct crparam *n)
+{
+       u_int32_t buf[64], i;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       bzero(buf, sizeof(buf));
+       bcopy(n->crp_p, buf, (n->crp_nbits + 7) / 8);
+
+       for (i = 0; i < len >> 2; i++)
+               WRITE_REG(sc, SAFE_PK_RAM_START + off + (i << 2),
+                   cpu_to_le32(buf[i]));
+}
+
+#ifdef SAFE_DEBUG
+static void
+safe_dump_dmastatus(struct safe_softc *sc, const char *tag)
+{
+       printf("%s: ENDIAN 0x%x SRC 0x%x DST 0x%x STAT 0x%x\n"
+               , tag
+               , READ_REG(sc, SAFE_DMA_ENDIAN)
+               , READ_REG(sc, SAFE_DMA_SRCADDR)
+               , READ_REG(sc, SAFE_DMA_DSTADDR)
+               , READ_REG(sc, SAFE_DMA_STAT)
+       );
+}
+
+static void
+safe_dump_intrstate(struct safe_softc *sc, const char *tag)
+{
+       printf("%s: HI_CFG 0x%x HI_MASK 0x%x HI_DESC_CNT 0x%x HU_STAT 0x%x HM_STAT 0x%x\n"
+               , tag
+               , READ_REG(sc, SAFE_HI_CFG)
+               , READ_REG(sc, SAFE_HI_MASK)
+               , READ_REG(sc, SAFE_HI_DESC_CNT)
+               , READ_REG(sc, SAFE_HU_STAT)
+               , READ_REG(sc, SAFE_HM_STAT)
+       );
+}
+
+static void
+safe_dump_ringstate(struct safe_softc *sc, const char *tag)
+{
+       u_int32_t estat = READ_REG(sc, SAFE_PE_ERNGSTAT);
+
+       /* NB: assume caller has lock on ring */
+       printf("%s: ERNGSTAT %x (next %u) back %lu front %lu\n",
+               tag,
+               estat, (estat >> SAFE_PE_ERNGSTAT_NEXT_S),
+               (unsigned long)(sc->sc_back - sc->sc_ring),
+               (unsigned long)(sc->sc_front - sc->sc_ring));
+}
+
+static void
+safe_dump_request(struct safe_softc *sc, const char* tag, struct safe_ringentry *re)
+{
+       int ix, nsegs;
+
+       ix = re - sc->sc_ring;
+       printf("%s: %p (%u): csr %x src %x dst %x sa %x len %x\n"
+               , tag
+               , re, ix
+               , re->re_desc.d_csr
+               , re->re_desc.d_src
+               , re->re_desc.d_dst
+               , re->re_desc.d_sa
+               , re->re_desc.d_len
+       );
+       if (re->re_src.nsegs > 1) {
+               ix = (re->re_desc.d_src - sc->sc_spalloc.dma_paddr) /
+                       sizeof(struct safe_pdesc);
+               for (nsegs = re->re_src.nsegs; nsegs; nsegs--) {
+                       printf(" spd[%u] %p: %p size %u flags %x"
+                               , ix, &sc->sc_spring[ix]
+                               , (caddr_t)(uintptr_t) sc->sc_spring[ix].pd_addr
+                               , sc->sc_spring[ix].pd_size
+                               , sc->sc_spring[ix].pd_flags
+                       );
+                       if (sc->sc_spring[ix].pd_size == 0)
+                               printf(" (zero!)");
+                       printf("\n");
+                       if (++ix == SAFE_TOTAL_SPART)
+                               ix = 0;
+               }
+       }
+       if (re->re_dst.nsegs > 1) {
+               ix = (re->re_desc.d_dst - sc->sc_dpalloc.dma_paddr) /
+                       sizeof(struct safe_pdesc);
+               for (nsegs = re->re_dst.nsegs; nsegs; nsegs--) {
+                       printf(" dpd[%u] %p: %p flags %x\n"
+                               , ix, &sc->sc_dpring[ix]
+                               , (caddr_t)(uintptr_t) sc->sc_dpring[ix].pd_addr
+                               , sc->sc_dpring[ix].pd_flags
+                       );
+                       if (++ix == SAFE_TOTAL_DPART)
+                               ix = 0;
+               }
+       }
+       printf("sa: cmd0 %08x cmd1 %08x staterec %x\n",
+               re->re_sa.sa_cmd0, re->re_sa.sa_cmd1, re->re_sa.sa_staterec);
+       printf("sa: key %x %x %x %x %x %x %x %x\n"
+               , re->re_sa.sa_key[0]
+               , re->re_sa.sa_key[1]
+               , re->re_sa.sa_key[2]
+               , re->re_sa.sa_key[3]
+               , re->re_sa.sa_key[4]
+               , re->re_sa.sa_key[5]
+               , re->re_sa.sa_key[6]
+               , re->re_sa.sa_key[7]
+       );
+       printf("sa: indigest %x %x %x %x %x\n"
+               , re->re_sa.sa_indigest[0]
+               , re->re_sa.sa_indigest[1]
+               , re->re_sa.sa_indigest[2]
+               , re->re_sa.sa_indigest[3]
+               , re->re_sa.sa_indigest[4]
+       );
+       printf("sa: outdigest %x %x %x %x %x\n"
+               , re->re_sa.sa_outdigest[0]
+               , re->re_sa.sa_outdigest[1]
+               , re->re_sa.sa_outdigest[2]
+               , re->re_sa.sa_outdigest[3]
+               , re->re_sa.sa_outdigest[4]
+       );
+       printf("sr: iv %x %x %x %x\n"
+               , re->re_sastate.sa_saved_iv[0]
+               , re->re_sastate.sa_saved_iv[1]
+               , re->re_sastate.sa_saved_iv[2]
+               , re->re_sastate.sa_saved_iv[3]
+       );
+       printf("sr: hashbc %u indigest %x %x %x %x %x\n"
+               , re->re_sastate.sa_saved_hashbc
+               , re->re_sastate.sa_saved_indigest[0]
+               , re->re_sastate.sa_saved_indigest[1]
+               , re->re_sastate.sa_saved_indigest[2]
+               , re->re_sastate.sa_saved_indigest[3]
+               , re->re_sastate.sa_saved_indigest[4]
+       );
+}
+
+static void
+safe_dump_ring(struct safe_softc *sc, const char *tag)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&sc->sc_ringmtx, flags);
+       printf("\nSafeNet Ring State:\n");
+       safe_dump_intrstate(sc, tag);
+       safe_dump_dmastatus(sc, tag);
+       safe_dump_ringstate(sc, tag);
+       if (sc->sc_nqchip) {
+               struct safe_ringentry *re = sc->sc_back;
+               do {
+                       safe_dump_request(sc, tag, re);
+                       if (++re == sc->sc_ringtop)
+                               re = sc->sc_ring;
+               } while (re != sc->sc_front);
+       }
+       spin_unlock_irqrestore(&sc->sc_ringmtx, flags);
+}
+#endif /* SAFE_DEBUG */
+
+
+static int safe_probe(struct pci_dev *dev, const struct pci_device_id *ent)
+{
+       struct safe_softc *sc = NULL;
+       u32 mem_start, mem_len, cmd;
+       int i, rc, devinfo;
+       dma_addr_t raddr;
+       static int num_chips = 0;
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       if (pci_enable_device(dev) < 0)
+               return(-ENODEV);
+
+       if (!dev->irq) {
+               printk("safe: found device with no IRQ assigned. check BIOS settings!");
+               pci_disable_device(dev);
+               return(-ENODEV);
+       }
+
+       if (pci_set_mwi(dev)) {
+               printk("safe: pci_set_mwi failed!");
+               return(-ENODEV);
+       }
+
+       sc = (struct safe_softc *) kmalloc(sizeof(*sc), GFP_KERNEL);
+       if (!sc)
+               return(-ENOMEM);
+       memset(sc, 0, sizeof(*sc));
+
+       softc_device_init(sc, "safe", num_chips, safe_methods);
+
+       sc->sc_irq = -1;
+       sc->sc_cid = -1;
+       sc->sc_pcidev = dev;
+       if (num_chips < SAFE_MAX_CHIPS) {
+               safe_chip_idx[device_get_unit(sc->sc_dev)] = sc;
+               num_chips++;
+       }
+
+       INIT_LIST_HEAD(&sc->sc_pkq);
+       spin_lock_init(&sc->sc_pkmtx);
+
+       pci_set_drvdata(sc->sc_pcidev, sc);
+
+       /* we read its hardware registers as memory */
+       mem_start = pci_resource_start(sc->sc_pcidev, 0);
+       mem_len   = pci_resource_len(sc->sc_pcidev, 0);
+
+       sc->sc_base_addr = (ocf_iomem_t) ioremap(mem_start, mem_len);
+       if (!sc->sc_base_addr) {
+               device_printf(sc->sc_dev, "failed to ioremap 0x%x-0x%x\n",
+                               mem_start, mem_start + mem_len - 1);
+               goto out;
+       }
+
+       /* fix up the bus size */
+       if (pci_set_dma_mask(sc->sc_pcidev, DMA_32BIT_MASK)) {
+               device_printf(sc->sc_dev, "No usable DMA configuration, aborting.\n");
+               goto out;
+       }
+       if (pci_set_consistent_dma_mask(sc->sc_pcidev, DMA_32BIT_MASK)) {
+               device_printf(sc->sc_dev, "No usable consistent DMA configuration, aborting.\n");
+               goto out;
+       }
+
+       pci_set_master(sc->sc_pcidev);
+
+       pci_read_config_dword(sc->sc_pcidev, PCI_COMMAND, &cmd);
+
+       if (!(cmd & PCI_COMMAND_MEMORY)) {
+               device_printf(sc->sc_dev, "failed to enable memory mapping\n");
+               goto out;
+       }
+
+       if (!(cmd & PCI_COMMAND_MASTER)) {
+               device_printf(sc->sc_dev, "failed to enable bus mastering\n");
+               goto out;
+       }
+
+       rc = request_irq(dev->irq, safe_intr, IRQF_SHARED, "safe", sc);
+       if (rc) {
+               device_printf(sc->sc_dev, "failed to hook irq %d\n", sc->sc_irq);
+               goto out;
+       }
+       sc->sc_irq = dev->irq;
+
+       sc->sc_chiprev = READ_REG(sc, SAFE_DEVINFO) &
+                       (SAFE_DEVINFO_REV_MAJ | SAFE_DEVINFO_REV_MIN);
+
+       /*
+        * Allocate packet engine descriptors.
+        */
+       sc->sc_ringalloc.dma_vaddr = pci_alloc_consistent(sc->sc_pcidev,
+                       SAFE_MAX_NQUEUE * sizeof (struct safe_ringentry),
+                       &sc->sc_ringalloc.dma_paddr);
+       if (!sc->sc_ringalloc.dma_vaddr) {
+               device_printf(sc->sc_dev, "cannot allocate PE descriptor ring\n");
+               goto out;
+       }
+
+       /*
+        * Hookup the static portion of all our data structures.
+        */
+       sc->sc_ring = (struct safe_ringentry *) sc->sc_ringalloc.dma_vaddr;
+       sc->sc_ringtop = sc->sc_ring + SAFE_MAX_NQUEUE;
+       sc->sc_front = sc->sc_ring;
+       sc->sc_back = sc->sc_ring;
+       raddr = sc->sc_ringalloc.dma_paddr;
+       bzero(sc->sc_ring, SAFE_MAX_NQUEUE * sizeof(struct safe_ringentry));
+       for (i = 0; i < SAFE_MAX_NQUEUE; i++) {
+               struct safe_ringentry *re = &sc->sc_ring[i];
+
+               re->re_desc.d_sa = raddr +
+                       offsetof(struct safe_ringentry, re_sa);
+               re->re_sa.sa_staterec = raddr +
+                       offsetof(struct safe_ringentry, re_sastate);
+
+               raddr += sizeof (struct safe_ringentry);
+       }
+       spin_lock_init(&sc->sc_ringmtx);
+
+       /*
+        * Allocate scatter and gather particle descriptors.
+        */
+       sc->sc_spalloc.dma_vaddr = pci_alloc_consistent(sc->sc_pcidev,
+                       SAFE_TOTAL_SPART * sizeof (struct safe_pdesc),
+                       &sc->sc_spalloc.dma_paddr);
+       if (!sc->sc_spalloc.dma_vaddr) {
+               device_printf(sc->sc_dev, "cannot allocate source particle descriptor ring\n");
+               goto out;
+       }
+       sc->sc_spring = (struct safe_pdesc *) sc->sc_spalloc.dma_vaddr;
+       sc->sc_springtop = sc->sc_spring + SAFE_TOTAL_SPART;
+       sc->sc_spfree = sc->sc_spring;
+       bzero(sc->sc_spring, SAFE_TOTAL_SPART * sizeof(struct safe_pdesc));
+
+       sc->sc_dpalloc.dma_vaddr = pci_alloc_consistent(sc->sc_pcidev,
+                       SAFE_TOTAL_DPART * sizeof (struct safe_pdesc),
+                       &sc->sc_dpalloc.dma_paddr);
+       if (!sc->sc_dpalloc.dma_vaddr) {
+               device_printf(sc->sc_dev, "cannot allocate destination particle descriptor ring\n");
+               goto out;
+       }
+       sc->sc_dpring = (struct safe_pdesc *) sc->sc_dpalloc.dma_vaddr;
+       sc->sc_dpringtop = sc->sc_dpring + SAFE_TOTAL_DPART;
+       sc->sc_dpfree = sc->sc_dpring;
+       bzero(sc->sc_dpring, SAFE_TOTAL_DPART * sizeof(struct safe_pdesc));
+
+       sc->sc_cid = crypto_get_driverid(softc_get_device(sc), CRYPTOCAP_F_HARDWARE);
+       if (sc->sc_cid < 0) {
+               device_printf(sc->sc_dev, "could not get crypto driver id\n");
+               goto out;
+       }
+
+       printf("%s:", device_get_nameunit(sc->sc_dev));
+
+       devinfo = READ_REG(sc, SAFE_DEVINFO);
+       if (devinfo & SAFE_DEVINFO_RNG) {
+               sc->sc_flags |= SAFE_FLAGS_RNG;
+               printf(" rng");
+       }
+       if (devinfo & SAFE_DEVINFO_PKEY) {
+               printf(" key");
+               sc->sc_flags |= SAFE_FLAGS_KEY;
+               crypto_kregister(sc->sc_cid, CRK_MOD_EXP, 0);
+#if 0
+               crypto_kregister(sc->sc_cid, CRK_MOD_EXP_CRT, 0);
+#endif
+               init_timer(&sc->sc_pkto);
+               sc->sc_pkto.function = safe_kpoll;
+               sc->sc_pkto.data = (unsigned long) device_get_unit(sc->sc_dev);
+       }
+       if (devinfo & SAFE_DEVINFO_DES) {
+               printf(" des/3des");
+               crypto_register(sc->sc_cid, CRYPTO_3DES_CBC, 0, 0);
+               crypto_register(sc->sc_cid, CRYPTO_DES_CBC, 0, 0);
+       }
+       if (devinfo & SAFE_DEVINFO_AES) {
+               printf(" aes");
+               crypto_register(sc->sc_cid, CRYPTO_AES_CBC, 0, 0);
+       }
+       if (devinfo & SAFE_DEVINFO_MD5) {
+               printf(" md5");
+               crypto_register(sc->sc_cid, CRYPTO_MD5_HMAC, 0, 0);
+       }
+       if (devinfo & SAFE_DEVINFO_SHA1) {
+               printf(" sha1");
+               crypto_register(sc->sc_cid, CRYPTO_SHA1_HMAC, 0, 0);
+       }
+       printf(" null");
+       crypto_register(sc->sc_cid, CRYPTO_NULL_CBC, 0, 0);
+       crypto_register(sc->sc_cid, CRYPTO_NULL_HMAC, 0, 0);
+       /* XXX other supported algorithms */
+       printf("\n");
+
+       safe_reset_board(sc);           /* reset h/w */
+       safe_init_board(sc);            /* init h/w */
+
+#if defined(CONFIG_OCF_RANDOMHARVEST) && !defined(SAFE_NO_RNG)
+       if (sc->sc_flags & SAFE_FLAGS_RNG) {
+               safe_rng_init(sc);
+               crypto_rregister(sc->sc_cid, safe_read_random, sc);
+       }
+#endif /* SAFE_NO_RNG */
+
+       return (0);
+
+out:
+       if (sc->sc_cid >= 0)
+               crypto_unregister_all(sc->sc_cid);
+       if (sc->sc_irq != -1)
+               free_irq(sc->sc_irq, sc);
+       if (sc->sc_ringalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_MAX_NQUEUE * sizeof (struct safe_ringentry),
+                               sc->sc_ringalloc.dma_vaddr, sc->sc_ringalloc.dma_paddr);
+       if (sc->sc_spalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_TOTAL_DPART * sizeof (struct safe_pdesc),
+                               sc->sc_spalloc.dma_vaddr, sc->sc_spalloc.dma_paddr);
+       if (sc->sc_dpalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_TOTAL_DPART * sizeof (struct safe_pdesc),
+                               sc->sc_dpalloc.dma_vaddr, sc->sc_dpalloc.dma_paddr);
+       kfree(sc);
+       return(-ENODEV);
+}
+
+static void safe_remove(struct pci_dev *dev)
+{
+       struct safe_softc *sc = pci_get_drvdata(dev);
+
+       DPRINTF(("%s()\n", __FUNCTION__));
+
+       /* XXX wait/abort active ops */
+
+       WRITE_REG(sc, SAFE_HI_MASK, 0);         /* disable interrupts */
+
+       del_timer_sync(&sc->sc_pkto);
+
+       crypto_unregister_all(sc->sc_cid);
+
+       safe_cleanchip(sc);
+
+       if (sc->sc_irq != -1)
+               free_irq(sc->sc_irq, sc);
+       if (sc->sc_ringalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_MAX_NQUEUE * sizeof (struct safe_ringentry),
+                               sc->sc_ringalloc.dma_vaddr, sc->sc_ringalloc.dma_paddr);
+       if (sc->sc_spalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_TOTAL_DPART * sizeof (struct safe_pdesc),
+                               sc->sc_spalloc.dma_vaddr, sc->sc_spalloc.dma_paddr);
+       if (sc->sc_dpalloc.dma_vaddr)
+               pci_free_consistent(sc->sc_pcidev,
+                               SAFE_TOTAL_DPART * sizeof (struct safe_pdesc),
+                               sc->sc_dpalloc.dma_vaddr, sc->sc_dpalloc.dma_paddr);
+       sc->sc_irq = -1;
+       sc->sc_ringalloc.dma_vaddr = NULL;
+       sc->sc_spalloc.dma_vaddr = NULL;
+       sc->sc_dpalloc.dma_vaddr = NULL;
+}
+
+static struct pci_device_id safe_pci_tbl[] = {
+       { PCI_VENDOR_SAFENET, PCI_PRODUCT_SAFEXCEL,
+         PCI_ANY_ID, PCI_ANY_ID, 0, 0, },
+       { },
+};
+MODULE_DEVICE_TABLE(pci, safe_pci_tbl);
+
+static struct pci_driver safe_driver = {
+       .name         = "safe",
+       .id_table     = safe_pci_tbl,
+       .probe        = safe_probe,
+       .remove       = safe_remove,
+       /* add PM stuff here one day */
+};
+
+static int __init safe_init (void)
+{
+       struct safe_softc *sc = NULL;
+       int rc;
+
+       DPRINTF(("%s(%p)\n", __FUNCTION__, safe_init));
+
+       rc = pci_register_driver(&safe_driver);
+       pci_register_driver_compat(&safe_driver, rc);
+
+       return rc;
+}
+
+static void __exit safe_exit (void)
+{
+       pci_unregister_driver(&safe_driver);
+}
+
+module_init(safe_init);
+module_exit(safe_exit);
+
+MODULE_LICENSE("BSD");
+MODULE_AUTHOR("David McCullough <david_mccullough@mcafee.com>");
+MODULE_DESCRIPTION("OCF driver for safenet PCI crypto devices");