Staging: add ced1401 USB driver
authorAlois Schlögl <alois.schloegl@ist.ac.at>
Tue, 18 Sep 2012 02:22:52 +0000 (19:22 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 18 Sep 2012 02:45:28 +0000 (19:45 -0700)
This was imported from the
http://pub.ist.ac.at/~schloegl/src/ced1401/.git git repo at the request
of Alois.  The driver originally came from Cambridge Electronic Design
Ltd and was authored by Greg P Smith and others, but Alois did the
maintance work to get it into a semi-building state and pushed to get it
into the main kernel tree here.

Cc: Alois Schlögl <alois.schloegl@ist.ac.at>
Cc: Greg P. Smith <greg@ced.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/ced1401/Makefile [new file with mode: 0644]
drivers/staging/ced1401/ced_ioc.c [new file with mode: 0644]
drivers/staging/ced1401/ced_ioctl.h [new file with mode: 0644]
drivers/staging/ced1401/machine.h [new file with mode: 0644]
drivers/staging/ced1401/usb1401.c [new file with mode: 0644]
drivers/staging/ced1401/usb1401.h [new file with mode: 0644]
drivers/staging/ced1401/use1401.h [new file with mode: 0644]
drivers/staging/ced1401/use14_ioc.h [new file with mode: 0644]
drivers/staging/ced1401/userspace/use1401.c [new file with mode: 0644]

diff --git a/drivers/staging/ced1401/Makefile b/drivers/staging/ced1401/Makefile
new file mode 100644 (file)
index 0000000..6acb031
--- /dev/null
@@ -0,0 +1,12 @@
+obj-m := cedusb.o
+cedusb-objs := usb1401.o ced_ioc.o
+KDIR := /lib/modules/$(shell uname -r)/build
+PWD := $(shell pwd)
+KBUILD_EXTRA_SYMBOLS := $(PWD)
+EXTRA_CFLAGS = -I$(HOME)/src/ced1401 
+all:
+       $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
+
+clean:
+       $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
+
diff --git a/drivers/staging/ced1401/ced_ioc.c b/drivers/staging/ced1401/ced_ioc.c
new file mode 100644 (file)
index 0000000..4a13c10
--- /dev/null
@@ -0,0 +1,1461 @@
+/* ced_ioc.c
+ ioctl part of the 1401 usb device driver for linux.
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/page-flags.h>
+#include <linux/pagemap.h>
+#include <linux/jiffies.h>
+
+#include "usb1401.h"
+
+/****************************************************************************
+** FlushOutBuff
+**
+** Empties the Output buffer and sets int lines. Used from user level only
+****************************************************************************/
+void FlushOutBuff(DEVICE_EXTENSION *pdx)
+{
+    dev_dbg(&pdx->interface->dev, "%s currentState=%d", __func__, pdx->sCurrentState);
+    if (pdx->sCurrentState == U14ERR_TIME)     /* Do nothing if hardware in trouble */
+        return;
+//    CharSend_Cancel(pdx);                   /* Kill off any pending I/O */
+    spin_lock_irq(&pdx->charOutLock);
+    pdx->dwNumOutput = 0;
+    pdx->dwOutBuffGet = 0;
+    pdx->dwOutBuffPut = 0;
+    spin_unlock_irq(&pdx->charOutLock);
+}
+
+/****************************************************************************
+**
+** FlushInBuff
+**
+** Empties the input buffer and sets int lines
+****************************************************************************/
+void FlushInBuff(DEVICE_EXTENSION *pdx)
+{
+    dev_dbg(&pdx->interface->dev, "%s currentState=%d", __func__, pdx->sCurrentState);
+    if (pdx->sCurrentState == U14ERR_TIME)     /* Do nothing if hardware in trouble */
+        return;
+//    CharRead_Cancel(pDevObject);            /* Kill off any pending I/O */
+    spin_lock_irq(&pdx->charInLock);
+    pdx->dwNumInput = 0;
+    pdx->dwInBuffGet = 0;
+    pdx->dwInBuffPut = 0;
+    spin_unlock_irq(&pdx->charInLock);
+}
+
+/****************************************************************************
+** PutChars
+**
+** Utility routine to copy chars into the output buffer and fire them off.
+** called from user mode, holds charOutLock.
+****************************************************************************/
+static int PutChars(DEVICE_EXTENSION* pdx, const char* pCh, unsigned int uCount)
+{
+    int iReturn;
+    spin_lock_irq(&pdx->charOutLock);   // get the output spin lock
+    if ((OUTBUF_SZ - pdx->dwNumOutput) >= uCount)
+    {
+        unsigned int u;
+        for (u=0; u<uCount; u++)
+        {
+            pdx->outputBuffer[pdx->dwOutBuffPut++] = pCh[u];
+            if (pdx->dwOutBuffPut >= OUTBUF_SZ)
+                pdx->dwOutBuffPut = 0;
+        }
+        pdx->dwNumOutput += uCount;
+        spin_unlock_irq(&pdx->charOutLock);
+        iReturn = SendChars(pdx);        // ...give a chance to transmit data
+    }
+    else
+    {
+        iReturn = U14ERR_NOOUT;          // no room at the out (ha-ha)
+        spin_unlock_irq(&pdx->charOutLock);
+    }
+    return iReturn;
+}
+
+/*****************************************************************************
+** Add the data in pData (local pointer) of length n to the output buffer, and
+** trigger an output transfer if this is appropriate. User mode.
+** Holds the io_mutex
+*****************************************************************************/
+int SendString(DEVICE_EXTENSION* pdx, const char __user* pData, unsigned int n)
+{
+    int iReturn = U14ERR_NOERROR;           // assume all will be well
+    char buffer[OUTBUF_SZ+1];               // space in our address space for characters
+    if (n > OUTBUF_SZ)                      // check space in local buffer...
+        return U14ERR_NOOUT;                // ...too many characters
+    if (copy_from_user(buffer, pData, n))
+        return -ENOMEM;                     // could not copy
+    buffer[n] = 0;                          // terminate for debug purposes
+
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    if (n > 0)                              // do nothing if nowt to do!
+    {
+        dev_dbg(&pdx->interface->dev, "%s n=%d>%s<", __func__, n, buffer);
+        iReturn = PutChars(pdx, buffer, n);
+    }
+
+    Allowi(pdx, false);                     // make sure we have input int
+    mutex_unlock(&pdx->io_mutex);
+
+   return iReturn;
+}
+
+/****************************************************************************
+** SendChar
+**
+** Sends a single character to the 1401. User mode, holds io_mutex.
+****************************************************************************/
+int SendChar(DEVICE_EXTENSION *pdx, char c)
+{
+    int iReturn;
+    mutex_lock(&pdx->io_mutex);         // Protect disconnect from new i/o
+    iReturn = PutChars(pdx, &c, 1);
+    dev_dbg(&pdx->interface->dev,"SendChar >%c< (0x%02x)", c, c);
+    Allowi(pdx, false);                 // Make sure char reads are running
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+/***************************************************************************
+**
+** Get1401State
+**
+**  Retrieves state information from the 1401, adjusts the 1401 state held
+**  in the device extension to indicate the current 1401 type.
+**
+**  *state is updated with information about the 1401 state as returned by the
+**         1401. The low byte is a code for what 1401 is doing:
+**
+**  0       normal 1401 operation
+**  1       sending chars to host
+**  2       sending block data to host
+**  3       reading block data from host
+**  4       sending an escape sequence to the host
+**  0x80    1401 is executing self-test, in which case the upper word
+**          is the last error code seen (or zero for no new error).
+**
+** *error is updated with error information if a self-test error code
+**          is returned in the upper word of state.
+**
+**  both state and error are set to -1 if there are comms problems, and
+**  to zero if there is a simple failure.
+**
+** return error code (U14ERR_NOERROR for OK)
+*/
+int Get1401State(DEVICE_EXTENSION* pdx, __u32* state, __u32* error)
+{
+    int nGot;
+    dev_dbg(&pdx->interface->dev, "Get1401State() entry");
+
+    *state = 0xFFFFFFFF;                        // Start off with invalid state
+    nGot = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+                           GET_STATUS, (D_TO_H|VENDOR|DEVREQ), 0,0,
+                           pdx->statBuf, sizeof(pdx->statBuf), HZ);
+    if (nGot != sizeof(pdx->statBuf))
+    {
+        dev_err(&pdx->interface->dev, "Get1401State() FAILED, return code %d", nGot);
+        pdx->sCurrentState = U14ERR_TIME;  // Indicate that things are very wrong indeed
+        *state = 0;                     // Force status values to a known state
+        *error = 0;
+    }
+    else
+    {
+        int nDevice;
+        dev_dbg(&pdx->interface->dev, "Get1401State() Success, state: 0x%x, 0x%x",
+                         pdx->statBuf[0], pdx->statBuf[1]);
+
+        *state = pdx->statBuf[0];       // Return the state values to the calling code
+        *error = pdx->statBuf[1];
+
+        nDevice = pdx->udev->descriptor.bcdDevice >> 8;    // 1401 type code value
+        switch (nDevice)                // so we can clean up current state
+        {
+        case 0:
+            pdx->sCurrentState = U14ERR_U1401;
+            break;
+
+        default:                        // allow lots of device codes for future 1401s
+            if ((nDevice >= 1) && (nDevice <= 23))
+                pdx->sCurrentState = (short)(nDevice + 6);
+            else
+                pdx->sCurrentState = U14ERR_ILL;
+            break;
+        }
+    }
+
+    return pdx->sCurrentState >= 0 ? U14ERR_NOERROR : pdx->sCurrentState;
+}
+
+/****************************************************************************
+** ReadWrite_Cancel
+**
+** Kills off staged read\write request from the USB if one is pending.
+****************************************************************************/
+int ReadWrite_Cancel(DEVICE_EXTENSION *pdx)
+{
+    dev_dbg(&pdx->interface->dev, "ReadWrite_Cancel entry %d", pdx->bStagedUrbPending);
+#ifdef NOT_WRITTEN_YET
+    int ntStatus = STATUS_SUCCESS;
+    bool bResult = false;
+    unsigned int i;
+    // We can fill this in when we know how we will implement the staged transfer stuff
+    spin_lock_irq(&pdx->stagedLock);
+
+    if (pdx->bStagedUrbPending)              // anything to be cancelled? May need more...
+    {
+        dev_info(&pdx->interface-dev, "ReadWrite_Cancel about to cancel Urb");
+
+ //       KeClearEvent(&pdx->StagingDoneEvent);   // Clear the staging done flag
+        USB_ASSERT(pdx->pStagedIrp != NULL);
+
+        // Release the spinlock first otherwise the completion routine may hang
+        //  on the spinlock while this function hands waiting for the event.
+        spin_unlock_irq(&pdx->stagedLock);
+        bResult = IoCancelIrp(pdx->pStagedIrp); // Actually do the cancel
+        if (bResult)
+        {
+            LARGE_INTEGER timeout;
+            timeout.QuadPart = -10000000;       // Use a timeout of 1 second
+            dev_info(&pdx->interface-dev, "ReadWrite_Cancel about to wait till done");
+            ntStatus = KeWaitForSingleObject(&pdx->StagingDoneEvent, Executive,
+                                             KernelMode, FALSE, &timeout);
+        }
+        else
+        {
+            dev_info(&pdx->interface-dev, "ReadWrite_Cancel, cancellation failed");
+            ntStatus = U14ERR_FAIL;
+        }
+        USB_KdPrint(DBGLVL_DEFAULT, ("ReadWrite_Cancel ntStatus = 0x%x decimal %d\n", ntStatus, ntStatus));
+    }
+    else
+        spin_unlock_irq(&pdx->stagedLock);
+
+    dev_info(&pdx->interface-dev, "ReadWrite_Cancel  done");
+    return ntStatus;
+#else
+    return U14ERR_NOERROR;
+#endif
+
+}
+
+/***************************************************************************
+** InSelfTest - utility to check in self test. Return 1 for ST, 0 for not or
+** a -ve error code if we failed for some reason.
+***************************************************************************/
+static int InSelfTest(DEVICE_EXTENSION* pdx, unsigned int* pState)
+{
+    unsigned int state, error;
+    int iReturn = Get1401State(pdx, &state, &error);    // see if in self-test
+    if (iReturn == U14ERR_NOERROR)                  // if all still OK
+         iReturn = (state == (unsigned int)-1) ||   // TX problem or...
+                   ((state & 0xff) == 0x80);        // ...self test
+    *pState = state;                                // return actual state
+    return iReturn;
+}
+
+/***************************************************************************
+** Is1401 - ALWAYS CALLED HOLDING THE io_mutex
+**
+** Tests for the current state of the 1401. Sets sCurrentState:
+**
+**  U14ERR_NOIF  1401  i/f card not installed (not done here)
+**  U14ERR_OFF   1401  apparently not switched on
+**  U14ERR_NC    1401  appears to be not connected
+**  U14ERR_ILL   1401  if it is there its not very well at all
+**  U14ERR_TIME  1401  appears OK, but doesn't communicate - very bad
+**  U14ERR_STD   1401  OK and ready for use
+**  U14ERR_PLUS  1401+ OK and ready for use
+**  U14ERR_U1401 Micro1401 OK and ready for use
+**  U14ERR_POWER Power1401 OK and ready for use
+**  U14ERR_U14012 Micro1401 mkII OK and ready for use
+**
+**  Returns TRUE if a 1401 detected and OK, else FALSE
+****************************************************************************/
+bool Is1401(DEVICE_EXTENSION* pdx)
+{
+    int iReturn;
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    ced_draw_down(pdx);                     // wait for, then kill outstanding Urbs
+    FlushInBuff(pdx);                       // Clear out input buffer & pipe
+    FlushOutBuff(pdx);                      // Clear output buffer & pipe
+
+    // The next call returns 0 if OK, but has returned 1 in the past, meaning that
+    // usb_unlock_device() is needed... now it always is
+    iReturn = usb_lock_device_for_reset(pdx->udev, pdx->interface);
+
+    // release the io_mutex because if we don't, we will deadlock due to system
+    // calls back into the driver.
+    mutex_unlock(&pdx->io_mutex);           // locked, so we will not get system calls
+    if (iReturn >= 0)                       // if we failed
+    {
+        iReturn = usb_reset_device(pdx->udev);  // try to do the reset
+        usb_unlock_device(pdx->udev);       // undo the lock
+    }
+
+    mutex_lock(&pdx->io_mutex);             // hold stuff off while we wait
+    pdx->dwDMAFlag = MODE_CHAR;             // Clear DMA mode flag regardless!
+    if (iReturn == 0)                       // if all is OK still
+    {
+        unsigned int state;
+        iReturn = InSelfTest(pdx, &state);  // see if likely in self test
+        if (iReturn > 0)                    // do we need to wait for self-test?
+        {
+            unsigned long ulTimeOut = jiffies + 30*HZ;  // when to give up
+            while((iReturn > 0) && time_before(jiffies, ulTimeOut))
+            {
+                schedule();                 // let other stuff run
+                iReturn = InSelfTest(pdx, &state);  // see if done yet
+            }
+        }
+
+        if (iReturn == 0)                   // if all is OK...
+            iReturn = state == 0;           // then sucess is that the state is 0
+    }
+    else
+        iReturn = 0;                        // we failed
+    pdx->bForceReset = false;               // Clear forced reset flag now
+
+    return iReturn > 0;
+}
+
+/****************************************************************************
+** QuickCheck  - ALWAYS CALLED HOLDING THE io_mutex
+** This is used to test for a 1401. It will try to do a quick check if all is
+**  OK, that is the 1401 was OK the last time it was asked, and there is no DMA
+**  in progress, and if the bTestBuff flag is set, the character buffers must be
+**  empty too. If the quick check shows that the state is still the same, then
+**  all is OK.
+**
+** If any of the above conditions are not met, or if the state or type of the
+**  1401 has changed since the previous test, the full Is1401 test is done, but
+**  only if bCanReset is also TRUE.
+**
+** The return value is TRUE if a useable 1401 is found, FALSE if not
+*/
+bool QuickCheck(DEVICE_EXTENSION* pdx, bool bTestBuff, bool bCanReset)
+{
+    bool bRet = false;           // assume it will fail and we will reset
+    bool bShortTest;
+
+    bShortTest = ((pdx->dwDMAFlag == MODE_CHAR) &&  // no DMA running
+                  (!pdx->bForceReset) &&            // Not had a real reset forced
+                  (pdx->sCurrentState >= U14ERR_STD));  // No 1401 errors stored
+
+    dev_dbg(&pdx->interface->dev, "%s DMAFlag:%d, state:%d, force:%d, testBuff:%d, short:%d",
+                     __func__, pdx->dwDMAFlag, pdx->sCurrentState, pdx->bForceReset, bTestBuff, bShortTest);
+
+    if ((bTestBuff) &&                              // Buffer check requested, and...
+        (pdx->dwNumInput || pdx->dwNumOutput))      // ...characters were in the buffer?
+    {
+        bShortTest = false;                         // Then do the full test
+        dev_dbg(&pdx->interface->dev, "%s will reset as buffers not empty", __func__);
+    }
+
+    if (bShortTest || !bCanReset)                   // Still OK to try the short test?
+    {                                               // Always test if no reset - we want state update
+        unsigned int state, error;
+        dev_dbg(&pdx->interface->dev, "%s->Get1401State", __func__);
+        if (Get1401State(pdx, &state, &error) == U14ERR_NOERROR) // Check on the 1401 state
+        {
+            if ((state & 0xFF) == 0)                // If call worked, check the status value
+                bRet = true;                        // If that was zero, all is OK, no reset needed
+        }
+    }
+
+    if (!bRet && bCanReset)                         // If all not OK, then
+    {
+        dev_info(&pdx->interface->dev, "%s->Is1401 %d %d %d %d",
+                 __func__, bShortTest, pdx->sCurrentState, bTestBuff, pdx->bForceReset);
+        bRet = Is1401(pdx);                         //  do full test
+    }
+
+    return bRet;
+}
+
+/****************************************************************************
+** Reset1401
+**
+** Resets the 1401 and empties the i/o buffers
+*****************************************************************************/
+int Reset1401(DEVICE_EXTENSION *pdx)
+{
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    dev_dbg(&pdx->interface->dev,"ABout to call QuickCheck");
+    QuickCheck(pdx, true, true);            // Check 1401, reset if not OK
+    mutex_unlock(&pdx->io_mutex);
+    return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** GetChar
+**
+** Gets a single character from the 1401
+****************************************************************************/
+int GetChar(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = U14ERR_NOIN;             // assume we will get  nothing
+    mutex_lock(&pdx->io_mutex);         // Protect disconnect from new i/o
+
+    dev_dbg(&pdx->interface->dev, "GetChar");
+
+    Allowi(pdx, false);                 // Make sure char reads are running
+    SendChars(pdx);                     // and send any buffered chars
+
+    spin_lock_irq(&pdx->charInLock);
+    if (pdx->dwNumInput > 0)            // worth looking
+    {
+        iReturn = pdx->inputBuffer[pdx->dwInBuffGet++];
+        if (pdx->dwInBuffGet >= INBUF_SZ)
+            pdx->dwInBuffGet = 0;
+        pdx->dwNumInput--;
+    }
+    else
+        iReturn = U14ERR_NOIN;             // no input data to read
+    spin_unlock_irq(&pdx->charInLock);
+
+    Allowi(pdx, false);                 // Make sure char reads are running
+
+    mutex_unlock(&pdx->io_mutex);       // Protect disconnect from new i/o
+    return iReturn;
+}
+
+/****************************************************************************
+** GetString
+**
+** Gets a string from the 1401. Returns chars up to the next CR or when
+** there are no more to read or nowhere to put them. CR is translated to
+** 0 and counted as a character. If the string does not end in a 0, we will
+** add one, if there is room, but it is not counted as a character.
+**
+** returns the count of characters (including the terminator, or 0 if none
+** or a negative error code.
+****************************************************************************/
+int GetString(DEVICE_EXTENSION *pdx, char __user* pUser, int n)
+{
+    int nAvailable;                         // character in the buffer
+    int iReturn = U14ERR_NOIN;
+    if (n <= 0)
+        return -ENOMEM;
+
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    Allowi(pdx, false);                     // Make sure char reads are running
+    SendChars(pdx);                         // and send any buffered chars
+
+    spin_lock_irq(&pdx->charInLock);
+    nAvailable = pdx->dwNumInput;           // characters available now
+    if (nAvailable > n)                     // read max of space in pUser...
+        nAvailable = n;                     // ...or input characters
+
+    if (nAvailable > 0)                     // worth looking?
+    {
+        char buffer[INBUF_SZ+1];            // space for a linear copy of data
+        int nGot = 0;
+        int nCopyToUser;                    // number to copy to user
+        char cData;
+        do
+        {
+            cData = pdx->inputBuffer[pdx->dwInBuffGet++];
+            if (cData == CR_CHAR)           // replace CR with zero
+                cData = (char)0;
+
+            if (pdx->dwInBuffGet >= INBUF_SZ)
+                pdx->dwInBuffGet = 0;       // wrap buffer pointer
+
+            buffer[nGot++] = cData;         // save the output
+        }
+        while((nGot < nAvailable) && cData);
+
+        nCopyToUser = nGot;                 // what to copy...
+        if (cData)                          // do we need null
+        {
+            buffer[nGot] = (char)0;         // make it tidy
+            if (nGot < n)                   // if space in user buffer...
+                ++nCopyToUser;              // ...copy the 0 as well.
+        }
+
+        pdx->dwNumInput -= nGot;
+        spin_unlock_irq(&pdx->charInLock);
+
+        dev_dbg(&pdx->interface->dev,"GetString read %d characters >%s<", nGot, buffer);
+        copy_to_user(pUser, buffer, nCopyToUser);
+
+        iReturn = nGot;                     // report characters read
+    }
+    else
+        spin_unlock_irq(&pdx->charInLock);
+
+    Allowi(pdx, false);                     // Make sure char reads are running
+    mutex_unlock(&pdx->io_mutex);           // Protect disconnect from new i/o
+
+    return iReturn;
+}
+
+/*******************************************************************************
+** Get count of characters in the inout buffer.
+*******************************************************************************/
+int Stat1401(DEVICE_EXTENSION *pdx)
+{
+    int iReturn;
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    Allowi(pdx, false);                     // make sure we allow pending chars
+    SendChars(pdx);                         // in both directions
+    iReturn = pdx->dwNumInput;              // no lock as single read
+    mutex_unlock(&pdx->io_mutex);           // Protect disconnect from new i/o
+    return iReturn;
+}
+
+/****************************************************************************
+** LineCount
+**
+** Returns the number of newline chars in the buffer. There is no need for
+** any fancy interlocks as we only read the interrupt routine data, and the
+** system is arranged so nothing can be destroyed.
+****************************************************************************/
+int LineCount(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = 0;                        // will be count of line ends
+
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    Allowi(pdx, false);                     // Make sure char reads are running
+    SendChars(pdx);                         // and send any buffered chars
+    spin_lock_irq(&pdx->charInLock);        // Get protection
+
+    if (pdx->dwNumInput > 0)                    // worth looking?
+    {
+        unsigned int dwIndex = pdx->dwInBuffGet;// start at first available
+        unsigned int dwEnd = pdx->dwInBuffPut;  // Position for search end
+        do
+        {
+            if (pdx->inputBuffer[dwIndex++] == CR_CHAR)
+                ++iReturn;                      // inc count if CR
+
+            if (dwIndex >= INBUF_SZ)            // see if we fall off buff
+                dwIndex = 0;
+        }
+        while (dwIndex != dwEnd);               // go to last avaliable
+    }
+
+    spin_unlock_irq(&pdx->charInLock);
+    dev_dbg(&pdx->interface->dev,"LineCount returned %d", iReturn);
+    mutex_unlock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    return iReturn;
+}
+
+/****************************************************************************
+** GetOutBufSpace
+**
+** Gets the space in the output buffer. Called from user code.
+*****************************************************************************/
+int GetOutBufSpace(DEVICE_EXTENSION *pdx)
+{
+    int iReturn;
+    mutex_lock(&pdx->io_mutex);             // Protect disconnect from new i/o
+    SendChars(pdx);                                 // send any buffered chars
+    iReturn = (int)(OUTBUF_SZ - pdx->dwNumOutput);  // no lock needed for single read
+    dev_dbg(&pdx->interface->dev,"OutBufSpace %d", iReturn);
+    mutex_unlock(&pdx->io_mutex);           // Protect disconnect from new i/o
+    return iReturn;
+}
+
+/****************************************************************************
+**
+** ClearArea
+**
+** Clears up a transfer area. This is always called in the context of a user
+** request, never from a call-back.
+****************************************************************************/
+int ClearArea(DEVICE_EXTENSION *pdx, int nArea)
+{
+    int iReturn = U14ERR_NOERROR;
+
+    if ((nArea < 0) || (nArea >= MAX_TRANSAREAS))
+    {
+        iReturn = U14ERR_BADAREA;
+        dev_err(&pdx->interface->dev, "%s Attempt to clear area %d", __func__, nArea);
+    }
+    else
+    {
+        TRANSAREA *pTA = &pdx->rTransDef[nArea]; // to save typing
+        if (!pTA->bUsed)                            // if not used...
+            iReturn = U14ERR_NOTSET;                // ...nothing to be done
+        else
+        {
+            // We must save the memory we return as we shouldn't mess with memory while
+            // holding a spin lock.
+            struct page **pPages = 0;               // save page address list
+            int nPages = 0;                         // and number of pages
+            int np;
+
+            dev_dbg(&pdx->interface->dev, "%s area %d", __func__, nArea);
+            spin_lock_irq(&pdx->stagedLock);
+            if ((pdx->StagedId == nArea) && (pdx->dwDMAFlag > MODE_CHAR))
+            {
+                iReturn = U14ERR_UNLOCKFAIL;               // cannot delete as in use
+                dev_err(&pdx->interface->dev, "%s call on area %d while active", __func__, nArea);
+            }
+            else
+            {
+                pPages = pTA->pPages;               // save page address list
+                nPages = pTA->nPages;               // and page count
+                if (pTA->dwEventSz)                 // if events flagging in use
+                    wake_up_interruptible(&pTA->wqEvent);   // release anything that was waiting
+
+                 if (pdx->bXFerWaiting && (pdx->rDMAInfo.wIdent == nArea))
+                    pdx->bXFerWaiting = false;           // Cannot have pending xfer if area cleared
+
+                // Clean out the TRANSAREA except for the wait queue, which is at the end
+                // This sets bUsed to false and dwEventSz to 0 to say area not used and no events.
+                memset(pTA, 0, sizeof(TRANSAREA)-sizeof(wait_queue_head_t));
+            }
+            spin_unlock_irq(&pdx->stagedLock);
+
+            if (pPages)                             // if we decided to release the memory
+            {
+                // Now we must undo the pinning down of the pages. We will assume the worst and mark
+                // all the pages as dirty. Don't be tempted to move this up above as you must not be
+                // holding a spin lock to do this stuff as it is not atomic.
+                dev_dbg(&pdx->interface->dev, "%s nPages=%d", __func__, nPages);
+
+                for (np = 0; np < nPages; ++np)
+                {
+                    if (pPages[np])
+                    {
+                        SetPageDirty(pPages[np]);
+                        page_cache_release(pPages[np]);
+                    }
+                }
+
+                kfree(pPages);
+                dev_dbg(&pdx->interface->dev, "%s kfree(pPages) done", __func__);
+            }
+        }
+    }
+
+    return iReturn;
+}
+
+/****************************************************************************
+** SetArea
+**
+** Sets up a transfer area - the functional part. Called by both
+** SetTransfer and SetCircular.
+****************************************************************************/
+static int SetArea(DEVICE_EXTENSION *pdx, int nArea, char __user* puBuf,
+                     unsigned int dwLength, bool bCircular, bool bCircToHost)
+{
+    // Start by working out the page aligned start of the area and the size
+    // of the area in pages, allowing for the start not being aligned and the
+    // end needing to be rounded up to a page boundary.
+    unsigned long ulStart = ((unsigned long)puBuf) & PAGE_MASK;
+    unsigned int ulOffset = ((unsigned long)puBuf) & (PAGE_SIZE-1);
+    int len = (dwLength + ulOffset+PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+    TRANSAREA *pTA = &pdx->rTransDef[nArea]; // to save typing
+    struct page **pPages = 0;               // space for page tables
+    int nPages = 0;                         // and number of pages
+
+    int iReturn = ClearArea(pdx, nArea);    // see if OK to use this area
+    if ((iReturn != U14ERR_NOTSET) &&          // if not area unused and...
+        (iReturn != U14ERR_NOERROR))           // ...not all OK, then...
+        return iReturn;                     // ...we cannot use this area
+
+    if (!access_ok(VERIFY_WRITE, puBuf, dwLength))  // if we cannot access the memory...
+        return -EFAULT;                     // ...then we are done
+
+    // Now allocate space to hold the page pointer and virtual address pointer tables
+    pPages = (struct page **)kmalloc(len*sizeof(struct page *), GFP_KERNEL);
+    if (!pPages)
+    {
+        iReturn = U14ERR_NOMEMORY;
+        goto error;
+    }
+    dev_dbg(&pdx->interface->dev, "%s %p, length=%06x, circular %d", __func__, puBuf, dwLength, bCircular);
+
+    // To pin down user pages we must first acquire the mapping semaphore.
+    down_read(&current->mm->mmap_sem);   // get memory map semaphore
+    nPages = get_user_pages(current, current->mm, ulStart, len, 1, 0, pPages, 0);
+    up_read(&current->mm->mmap_sem);     // release the semaphore
+    dev_dbg(&pdx->interface->dev, "%s nPages = %d", __func__, nPages);
+
+    if (nPages > 0)                     // if we succeeded
+    {
+        // If you are tempted to use page_address (form LDD3), forget it. You MUST use
+        // kmap() or kmap_atomic() to get a virtual address. page_address will give you
+        // (null) or at least it does in this context with an x86 machine.
+        spin_lock_irq(&pdx->stagedLock);
+        pTA->lpvBuff = puBuf;           // keep start of region (user address)
+        pTA->dwBaseOffset = ulOffset;   // save offset in first page to start of xfer
+        pTA->dwLength = dwLength;       // Size if the region in bytes
+        pTA->pPages = pPages;           // list of pages that are used by buffer
+        pTA->nPages = nPages;           // number of pages
+
+        pTA->bCircular = bCircular;
+        pTA->bCircToHost = bCircToHost;
+
+        pTA->aBlocks[0].dwOffset = 0;
+        pTA->aBlocks[0].dwSize = 0;
+        pTA->aBlocks[1].dwOffset = 0;
+        pTA->aBlocks[1].dwSize = 0;
+        pTA->bUsed = true;              // This is now a used block
+
+        spin_unlock_irq(&pdx->stagedLock);
+        iReturn = U14ERR_NOERROR;          // say all was well
+    }
+    else
+    {
+        iReturn = U14ERR_LOCKFAIL;
+        goto error;
+    }
+
+    return iReturn;
+
+error:
+    kfree(pPages);
+    return iReturn;
+}
+
+/****************************************************************************
+** SetTransfer
+**
+** Sets up a transfer area record. If the area is already set, we attempt to
+** unset it. Unsetting will fail if the area is booked, and a transfer to that
+** area is in progress. Otherwise, we will release the area and re-assign it.
+****************************************************************************/
+int SetTransfer(DEVICE_EXTENSION *pdx, TRANSFERDESC __user *pTD)
+{
+    int iReturn;
+    TRANSFERDESC td;
+    copy_from_user(&td, pTD, sizeof(td));
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev,"%s area:%d, size:%08x", __func__, td.wAreaNum, td.dwLength);
+    // The strange cast is done so that we don't get warnings in 32-bit linux about the size of the
+    // pointer. The pointer is always passed as a 64-bit object so that we don't have problems using
+    // a 32-bit program on a 64-bit system. unsigned long is 64-bits on a 64-bit system.
+    iReturn = SetArea(pdx, td.wAreaNum, (char __user *)((unsigned long)td.lpvBuff), td.dwLength, false, false);
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+/****************************************************************************
+** UnSetTransfer
+** Erases a transfer area record
+****************************************************************************/
+int UnsetTransfer(DEVICE_EXTENSION *pdx, int nArea)
+{
+    int iReturn;
+    mutex_lock(&pdx->io_mutex);
+    iReturn = ClearArea(pdx, nArea);
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+/****************************************************************************
+** SetEvent
+** Creates an event that we can test for based on a transfer to/from an area.
+** The area must be setup for a transfer. We attempt to simulate the Windows
+** driver behavior for events (as we don't actually use them), which is to
+** pretend that whatever the user asked for was achieved, so we return 1 if
+** try to create one, and 0 if they ask to remove (assuming all else was OK).
+****************************************************************************/
+int SetEvent(DEVICE_EXTENSION *pdx, TRANSFEREVENT __user*pTE)
+{
+    int iReturn = U14ERR_NOERROR;
+    TRANSFEREVENT te;
+    copy_from_user(&te, pTE, sizeof(te));   // get a local copy of the data
+    if (te.wAreaNum >= MAX_TRANSAREAS)      // the area must exist
+        return U14ERR_BADAREA;
+    else
+    {
+        TRANSAREA *pTA = &pdx->rTransDef[te.wAreaNum];
+        mutex_lock(&pdx->io_mutex);         // make sure we have no competitor
+        spin_lock_irq(&pdx->stagedLock);
+        if (pTA->bUsed)                     // area must be in use
+        {
+            pTA->dwEventSt = te.dwStart;    // set area regions
+            pTA->dwEventSz = te.dwLength;   // set size (0 cancels it)
+            pTA->bEventToHost = te.wFlags & 1; // set the direction
+            pTA->iWakeUp = 0;               // zero the wake up count
+        }
+        else
+            iReturn = U14ERR_NOTSET;
+        spin_unlock_irq(&pdx->stagedLock);
+        mutex_unlock(&pdx->io_mutex);
+    }
+    return iReturn == U14ERR_NOERROR ? (te.iSetEvent ? 1 : U14ERR_NOERROR) : iReturn;
+}
+
+/****************************************************************************
+** WaitEvent
+** Sleep the process with a timeout waiting for an event. Returns the number
+** of times that a block met the event condition since we last cleared it or
+** 0 if timed out, or -ve error (bad area or not set, or signal).
+****************************************************************************/
+int WaitEvent(DEVICE_EXTENSION *pdx, int nArea, int msTimeOut)
+{
+    int iReturn;
+    if ((unsigned)nArea > MAX_TRANSAREAS)
+        return U14ERR_BADAREA;
+    else
+    {
+        int iWait;
+        TRANSAREA *pTA = &pdx->rTransDef[nArea];
+        msTimeOut = (msTimeOut * HZ + 999)/1000; // convert timeout to jiffies
+
+        // We cannot wait holding the mutex, but we check the flags while holding
+        // it. This may well be pointless as another thread could get in between
+        // releasing it and the wait call. However, this would have to clear the
+        // iWakeUp flag. However, the !pTA-bUsed may help us in this case.
+        mutex_lock(&pdx->io_mutex);         // make sure we have no competitor
+        if (!pTA->bUsed || !pTA->dwEventSz) // check something to wait for...
+            return U14ERR_NOTSET;           // ...else we do nothing
+        mutex_unlock(&pdx->io_mutex);
+
+        if (msTimeOut)
+            iWait = wait_event_interruptible_timeout(pTA->wqEvent, pTA->iWakeUp || !pTA->bUsed, msTimeOut);
+        else
+            iWait = wait_event_interruptible(pTA->wqEvent, pTA->iWakeUp || !pTA->bUsed);
+        if (iWait)
+            iReturn = -ERESTARTSYS;         // oops - we have had a SIGNAL
+        else
+            iReturn = pTA->iWakeUp;         // else the wakeup count
+
+        spin_lock_irq(&pdx->stagedLock);
+        pTA->iWakeUp = 0;                   // clear the flag
+        spin_unlock_irq(&pdx->stagedLock);
+    }
+    return iReturn;
+}
+
+/****************************************************************************
+** TestEvent
+** Test the event to see if a WaitEvent would return immediately. Returns the
+** number of times a block completed since the last call, or 0 if none or a
+** negative error.
+****************************************************************************/
+int TestEvent(DEVICE_EXTENSION *pdx, int nArea)
+{
+    int iReturn;
+    if ((unsigned)nArea > MAX_TRANSAREAS)
+        iReturn = U14ERR_BADAREA;
+    else
+    {
+        TRANSAREA *pTA = &pdx->rTransDef[nArea];
+        mutex_lock(&pdx->io_mutex);         // make sure we have no competitor
+        spin_lock_irq(&pdx->stagedLock);
+        iReturn = pTA->iWakeUp;             // get wakeup count since last call
+        pTA->iWakeUp = 0;                   // clear the count
+        spin_unlock_irq(&pdx->stagedLock);
+        mutex_unlock(&pdx->io_mutex);
+    }
+    return iReturn;
+}
+
+/****************************************************************************
+** GetTransferInfo
+** Puts the current state of the 1401 in a TGET_TX_BLOCK.
+*****************************************************************************/
+int GetTransfer(DEVICE_EXTENSION *pdx, TGET_TX_BLOCK __user *pTX)
+{
+    int iReturn = U14ERR_NOERROR;
+    unsigned int dwIdent;
+
+    mutex_lock(&pdx->io_mutex);
+    dwIdent = pdx->StagedId;                // area ident for last xfer
+    if (dwIdent >= MAX_TRANSAREAS)
+        iReturn = U14ERR_BADAREA;
+    else
+    {
+        // Return the best information we have - we don't have physical addresses
+        TGET_TX_BLOCK tx;
+        memset(&tx, 0, sizeof(tx));         // clean out local work structure
+        tx.size = pdx->rTransDef[dwIdent].dwLength;
+        tx.linear = (long long)((long)pdx->rTransDef[dwIdent].lpvBuff);
+        tx.avail = GET_TX_MAXENTRIES;       // how many blocks we could return
+        tx.used = 1;                        // number we actually return
+        tx.entries[0].physical = (long long)(tx.linear+pdx->StagedOffset);
+        tx.entries[0].size = tx.size;
+        copy_to_user(pTX, &tx, sizeof(tx));
+    }
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+/****************************************************************************
+** KillIO1401
+**
+** Empties the host i/o buffers
+****************************************************************************/
+int KillIO1401(DEVICE_EXTENSION *pdx)
+{
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    mutex_lock(&pdx->io_mutex);
+    FlushOutBuff(pdx);
+    FlushInBuff(pdx);
+    mutex_unlock(&pdx->io_mutex);
+    return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** BlkTransState
+** Returns a 0 or a 1 for whether DMA is happening. No point holding a mutex
+** for this as it only does one read.
+*****************************************************************************/
+int BlkTransState(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = pdx->dwDMAFlag != MODE_CHAR;
+    dev_dbg(&pdx->interface->dev, "%s = %d", __func__, iReturn);
+    return iReturn;
+}
+
+/****************************************************************************
+** StateOf1401
+**
+** Puts the current state of the 1401 in the Irp return buffer.
+*****************************************************************************/
+int StateOf1401(DEVICE_EXTENSION *pdx)
+{
+    int iReturn;
+    mutex_lock(&pdx->io_mutex);
+
+    QuickCheck(pdx, false, false);    // get state up to date, no reset
+    iReturn = pdx->sCurrentState;
+
+    mutex_unlock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s = %d", __func__, iReturn);
+
+    return iReturn;
+}
+/****************************************************************************
+** StartSelfTest
+**
+** Initiates a self-test cycle. The assumption is that we have no interrupts
+** active, so we should make sure that this is the case.
+*****************************************************************************/
+int StartSelfTest(DEVICE_EXTENSION *pdx)
+{
+    int nGot;
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    ced_draw_down(pdx);                     // wait for, then kill outstanding Urbs
+    FlushInBuff(pdx);                       // Clear out input buffer & pipe
+    FlushOutBuff(pdx);                      // Clear output buffer & pipe
+//    ReadWrite_Cancel(pDeviceObject);        /* so things stay tidy */
+    pdx->dwDMAFlag = MODE_CHAR;             /* Clear DMA mode flags here */
+
+    nGot = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+                       DB_SELFTEST, (H_TO_D|VENDOR|DEVREQ), 0, 0,
+                       0, 0, HZ);           // allow 1 second timeout
+    pdx->ulSelfTestTime = jiffies + HZ*30;  // 30 seconds into the future
+
+    mutex_unlock(&pdx->io_mutex);
+    if (nGot < 0)
+        dev_err(&pdx->interface->dev, "%s err=%d", __func__, nGot);
+    return nGot < 0 ? U14ERR_FAIL : U14ERR_NOERROR;
+}
+
+
+/****************************************************************************
+** CheckSelfTest
+**
+** Check progress of a self-test cycle
+****************************************************************************/
+int CheckSelfTest(DEVICE_EXTENSION *pdx, TGET_SELFTEST __user *pGST)
+{
+    unsigned int state, error;
+    int iReturn;
+    TGET_SELFTEST gst;                      // local work space
+    memset(&gst, 0, sizeof(gst));           // clear out the space (sets code 0)
+
+    mutex_lock(&pdx->io_mutex);
+
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    iReturn = Get1401State(pdx, &state, &error);
+    if (iReturn == U14ERR_NOERROR)             // Only accept zero if it happens twice
+        iReturn = Get1401State(pdx, &state, &error);
+
+    if (iReturn != U14ERR_NOERROR)             // Self-test can cause comms errors
+    {                                       // so we assume still testing
+        dev_err(&pdx->interface->dev, "%s Get1401State=%d, assuming still testing", __func__, iReturn);
+        state = 0x80;                       // Force still-testing, no error
+        error = 0;
+        iReturn = U14ERR_NOERROR;
+    }
+
+    if ((state == -1) && (error == -1))     // If Get1401State had problems
+    {
+        dev_err(&pdx->interface->dev, "%s Get1401State failed, assuming still testing", __func__);
+        state = 0x80;                       // Force still-testing, no error
+        error = 0;
+    }
+
+    if ((state & 0xFF) == 0x80)             // If we are still in self-test
+    {
+        if (state & 0x00FF0000)             // Have we got an error?
+        {
+            gst.code = (state & 0x00FF0000) >> 16;  // read the error code
+            gst.x = error & 0x0000FFFF;             // Error data X
+            gst.y = (error & 0xFFFF0000) >> 16;     // and data Y
+            dev_dbg(&pdx->interface->dev,"Self-test error code %d", gst.code);
+        }
+        else                                // No error, check for timeout
+        {
+            unsigned long ulNow = jiffies;  // get current time
+            if (time_after(ulNow, pdx->ulSelfTestTime))
+            {
+                gst.code = -2;              // Flag the timeout
+                dev_dbg(&pdx->interface->dev, "Self-test timed-out");
+            }
+            else
+                dev_dbg(&pdx->interface->dev, "Self-test on-going");
+        }
+    }
+    else
+    {
+        gst.code = -1;                      // Flag the test is done
+        dev_dbg(&pdx->interface->dev, "Self-test done");
+    }
+
+    if (gst.code < 0)                       // If we have a problem or finished
+    {                                       // If using the 2890 we should reset properly
+        if ((pdx->nPipes == 4) && (pdx->s1401Type <= TYPEPOWER))
+            Is1401(pdx);                    // Get 1401 reset and OK
+        else
+            QuickCheck(pdx, true, true);    // Otherwise check without reset unless problems
+    }
+    mutex_unlock(&pdx->io_mutex);
+
+    copy_to_user(pGST, &gst, sizeof(gst));  // copy result to user space
+    return iReturn;
+}
+
+/****************************************************************************
+** TypeOf1401
+**
+** Returns code for standard, plus, micro1401, power1401 or none
+****************************************************************************/
+int TypeOf1401(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = TYPEUNKNOWN;
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    switch (pdx->s1401Type)
+    {
+    case TYPE1401: iReturn = U14ERR_STD;  break;   // Handle these types directly
+    case TYPEPLUS: iReturn = U14ERR_PLUS; break;
+    case TYPEU1401:iReturn = U14ERR_U1401;break;
+    default:
+        if ((pdx->s1401Type >= TYPEPOWER) &&
+            (pdx->s1401Type <= 25))
+            iReturn = pdx->s1401Type + 4;       // We can calculate types
+        else                                    //  for up-coming 1401 designs
+            iReturn = TYPEUNKNOWN;              // Don't know or not there
+    }
+    dev_dbg(&pdx->interface->dev, "%s %d", __func__, iReturn);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+/****************************************************************************
+** TransferFlags
+**
+** Returns flags on block transfer abilities
+****************************************************************************/
+int TransferFlags(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = U14TF_MULTIA | U14TF_DIAG |   // we always have multiple DMA area
+                  U14TF_NOTIFY | U14TF_CIRCTH;  // diagnostics, notify and circular
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    mutex_lock(&pdx->io_mutex);
+    if (pdx->bIsUSB2)                           // Set flag for USB2 if appropriate
+        iReturn |= U14TF_USB2;
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+/***************************************************************************
+** DbgCmd1401
+** Issues a debug\diagnostic command to the 1401 along with a 32-bit datum
+** This is a utility command used for dbg operations.
+*/
+static int DbgCmd1401(DEVICE_EXTENSION *pdx, unsigned char cmd, unsigned int data)
+{
+    int iReturn;
+    dev_dbg(&pdx->interface->dev, "%s entry", __func__);
+    iReturn = usb_control_msg(pdx->udev, usb_sndctrlpipe(pdx->udev, 0),
+                       cmd, (H_TO_D|VENDOR|DEVREQ),
+                       (unsigned short)data, (unsigned short)(data >> 16),
+                       0, 0, HZ);           // allow 1 second timeout
+    if (iReturn < 0)
+        dev_err(&pdx->interface->dev, "%s fail code=%d", __func__, iReturn);
+
+    return iReturn;
+}
+
+/****************************************************************************
+** DbgPeek
+**
+** Execute the diagnostic peek operation. Uses address, width and repeats.
+****************************************************************************/
+int DbgPeek(DEVICE_EXTENSION *pdx, TDBGBLOCK __user* pDB)
+{
+    int iReturn;
+    TDBGBLOCK db;
+    copy_from_user(&db, pDB, sizeof(db));   // get the data
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s @ %08x", __func__, db.iAddr);
+
+    iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+    if (iReturn == U14ERR_NOERROR)
+       iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+    if (iReturn == U14ERR_NOERROR)
+       iReturn = DbgCmd1401(pdx, DB_PEEK, 0);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+
+/****************************************************************************
+** DbgPoke
+**
+** Execute the diagnostic poke operation. Parameters are in the CSBLOCK struct
+** in order address, size, repeats and value to poke.
+****************************************************************************/
+int DbgPoke(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+    int iReturn;
+    TDBGBLOCK db;
+    copy_from_user(&db, pDB, sizeof(db));   // get the data
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s @ %08x", __func__, db.iAddr);
+
+    iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_POKE, db.iData);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+
+/****************************************************************************
+** DbgRampData
+**
+** Execute the diagnostic ramp data operation. Parameters are in the CSBLOCK struct
+** in order address, default, enable mask, size and repeats.
+****************************************************************************/
+int DbgRampData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+    int iReturn;
+    TDBGBLOCK db;
+    copy_from_user(&db, pDB, sizeof(db));   // get the data
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s @ %08x", __func__, db.iAddr);
+
+    iReturn = DbgCmd1401(pdx, DB_SETADD, db.iAddr);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_SETDEF, db.iDefault);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_SETMASK, db.iMask);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_RAMPD, 0);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+
+/****************************************************************************
+** DbgRampAddr
+**
+** Execute the diagnostic ramp address operation
+****************************************************************************/
+int DbgRampAddr(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+    int iReturn;
+    TDBGBLOCK db;
+    copy_from_user(&db, pDB, sizeof(db));   // get the data
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    iReturn = DbgCmd1401(pdx, DB_SETDEF, db.iDefault);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_SETMASK, db.iMask);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_WIDTH, db.iWidth);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_REPEATS, db.iRepeats);
+    if (iReturn == U14ERR_NOERROR)
+        iReturn = DbgCmd1401(pdx, DB_RAMPA, 0);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+
+/****************************************************************************
+** DbgGetData
+**
+** Retrieve the data resulting from the last debug Peek operation
+****************************************************************************/
+int DbgGetData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB)
+{
+    int iReturn;
+    TDBGBLOCK db;
+    memset(&db, 0, sizeof(db));     // fill returned block with 0s
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    // Read back the last peeked value from the 1401.
+    iReturn = usb_control_msg(pdx->udev, usb_rcvctrlpipe(pdx->udev, 0),
+                           DB_DATA, (D_TO_H|VENDOR|DEVREQ), 0,0,
+                           &db.iData, sizeof(db.iData), HZ);
+    if (iReturn == sizeof(db.iData))
+    {
+        copy_to_user(pDB, &db, sizeof(db));
+        iReturn = U14ERR_NOERROR;
+    }
+    else
+        dev_err(&pdx->interface->dev, "%s failed, code %d", __func__, iReturn);
+
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+/****************************************************************************
+** DbgStopLoop
+**
+** Stop any never-ending debug loop, we just call Get1401State for USB
+**
+****************************************************************************/
+int DbgStopLoop(DEVICE_EXTENSION *pdx)
+{
+    int iReturn;
+    unsigned int uState, uErr;
+
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    iReturn = Get1401State(pdx, &uState, &uErr);
+    mutex_unlock(&pdx->io_mutex);
+
+    return iReturn;
+}
+
+
+/****************************************************************************
+** SetCircular
+**
+** Sets up a transfer area record for circular transfers. If the area is
+** already set, we attempt to unset it. Unsetting will fail if the area is
+** booked and a transfer to that area is in progress. Otherwise, we will
+** release the area and re-assign it.
+****************************************************************************/
+int SetCircular(DEVICE_EXTENSION *pdx, TRANSFERDESC __user *pTD)
+{
+    int iReturn;
+    bool bToHost;
+    TRANSFERDESC td;
+    copy_from_user(&td, pTD, sizeof(td));
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev,"%s area:%d, size:%08x", __func__, td.wAreaNum, td.dwLength);
+    bToHost = td.eSize != 0;    // this is used as the tohost flag
+
+    // The strange cast is done so that we don't get warnings in 32-bit linux about the size of the
+    // pointer. The pointer is always passed as a 64-bit object so that we don't have problems using
+    // a 32-bit program on a 64-bit system. unsigned long is 64-bits on a 64-bit system.
+    iReturn = SetArea(pdx, td.wAreaNum, (char __user *)((unsigned long)td.lpvBuff), td.dwLength, true, bToHost);
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+/****************************************************************************
+** GetCircBlock
+**
+** Return the next available block of circularly-transferred data.
+****************************************************************************/
+int GetCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user* pCB)
+{
+    int iReturn = U14ERR_NOERROR;
+    unsigned int nArea;
+    TCIRCBLOCK cb;
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    copy_from_user(&cb, pCB, sizeof(cb));
+    mutex_lock(&pdx->io_mutex);
+
+    nArea = cb.nArea;                       // Retrieve parameters first
+    cb.dwOffset = 0;                        // set default result (nothing)
+    cb.dwSize = 0;
+
+    if (nArea < MAX_TRANSAREAS)             // The area number must be OK
+    {
+        TRANSAREA* pArea = &pdx->rTransDef[nArea];  // Pointer to relevant info
+        spin_lock_irq(&pdx->stagedLock);            // Lock others out
+
+        if ((pArea->bUsed) && (pArea->bCircular) && // Must be circular area
+            (pArea->bCircToHost))                   // For now at least must be to host
+        {
+            if (pArea->aBlocks[0].dwSize > 0)       // Got anything?
+            {
+                cb.dwOffset = pArea->aBlocks[0].dwOffset;
+                cb.dwSize = pArea->aBlocks[0].dwSize;
+                dev_dbg(&pdx->interface->dev, "%s return block 0: %d bytes at %d", __func__, cb.dwSize, cb.dwOffset);
+            }
+        }
+        else
+            iReturn = U14ERR_NOTSET;
+
+        spin_unlock_irq(&pdx->stagedLock);
+    }
+    else
+        iReturn = U14ERR_BADAREA;
+
+    copy_to_user(pCB, &cb, sizeof(cb));
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+
+/****************************************************************************
+** FreeCircBlock
+**
+** Frees a block of circularly-transferred data and returns the next one.
+****************************************************************************/
+int FreeCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user* pCB)
+{
+    int iReturn = U14ERR_NOERROR;
+    unsigned int nArea, uStart, uSize;
+    TCIRCBLOCK cb;
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    copy_from_user(&cb, pCB, sizeof(cb));
+    mutex_lock(&pdx->io_mutex);
+
+    nArea = cb.nArea;                               // Retrieve parameters first
+    uStart = cb.dwOffset;
+    uSize = cb.dwSize;
+    cb.dwOffset = 0;                                // then set default result (nothing)
+    cb.dwSize = 0;
+
+    if (nArea < MAX_TRANSAREAS)                     // The area number must be OK
+    {
+        TRANSAREA* pArea = &pdx->rTransDef[nArea];  // Pointer to relevant info
+        spin_lock_irq(&pdx->stagedLock);            // Lock others out
+
+        if ((pArea->bUsed) && (pArea->bCircular) && // Must be circular area
+            (pArea->bCircToHost))                   // For now at least must be to host
+        {
+            bool bWaiting = false;
+
+            if ((pArea->aBlocks[0].dwSize >= uSize) &&  // Got anything?
+                (pArea->aBlocks[0].dwOffset == uStart)) // Must be legal data
+            {
+                pArea->aBlocks[0].dwSize -= uSize;
+                pArea->aBlocks[0].dwOffset += uSize;
+                if (pArea->aBlocks[0].dwSize == 0)  // Have we emptied this block?
+                {
+                    if (pArea->aBlocks[1].dwSize)   // Is there a second block?
+                    {
+                        pArea->aBlocks[0] = pArea->aBlocks[1];  // Copy down block 2 data
+                        pArea->aBlocks[1].dwSize = 0;   // and mark the second block as unused
+                        pArea->aBlocks[1].dwOffset = 0;
+                    }
+                    else
+                        pArea->aBlocks[0].dwOffset = 0;
+                }
+
+                dev_dbg(&pdx->interface->dev, "%s free %d bytes at %d, return %d bytes at %d, wait=%d",
+                         __func__, uSize, uStart, pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset, pdx->bXFerWaiting);
+
+                // Return the next available block of memory as well
+                if (pArea->aBlocks[0].dwSize > 0)   // Got anything?
+                {
+                    cb.dwOffset = pArea->aBlocks[0].dwOffset;
+                    cb.dwSize = pArea->aBlocks[0].dwSize;
+                }
+
+                bWaiting = pdx->bXFerWaiting;
+                if (bWaiting && pdx->bStagedUrbPending)
+                {
+                    dev_err(&pdx->interface->dev, "%s ERROR: waiting xfer and staged Urb pending!", __func__);
+                    bWaiting = false;
+                }
+            }
+            else
+            {
+                dev_err(&pdx->interface->dev, "%s ERROR: freeing %d bytes at %d, block 0 is %d bytes at %d",
+                         __func__, uSize, uStart, pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset);
+                 iReturn = U14ERR_NOMEMORY;
+            }
+
+            // If we have one, kick off pending transfer
+            if (bWaiting)                       // Got a block xfer waiting?
+            {
+                int RWMStat = ReadWriteMem(pdx, !pdx->rDMAInfo.bOutWard,
+                                      pdx->rDMAInfo.wIdent, pdx->rDMAInfo.dwOffset, pdx->rDMAInfo.dwSize);
+                if (RWMStat != U14ERR_NOERROR)
+                    dev_err(&pdx->interface->dev, "%s rw setup failed %d", __func__, RWMStat);
+            }
+        }
+        else
+            iReturn = U14ERR_NOTSET;
+
+        spin_unlock_irq(&pdx->stagedLock);
+    }
+    else
+        iReturn = U14ERR_BADAREA;
+
+    copy_to_user(pCB, &cb, sizeof(cb));
+    mutex_unlock(&pdx->io_mutex);
+    return iReturn;
+}
+
+
+
diff --git a/drivers/staging/ced1401/ced_ioctl.h b/drivers/staging/ced1401/ced_ioctl.h
new file mode 100644 (file)
index 0000000..075ecad
--- /dev/null
@@ -0,0 +1,232 @@
+/* ced_ioctl.h
+ IOCTL calls for the CED1401 driver
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+#ifndef __CED_IOCTL_H__
+#define __CED_IOCTL_H__
+#include <asm/ioctl.h>
+
+/// dma modes, only MODE_CHAR and MODE_LINEAR are used in this driver
+#define MODE_CHAR           0
+#define MODE_LINEAR         1
+
+/****************************************************************************
+** TypeDefs
+*****************************************************************************/
+
+typedef unsigned short TBLOCKENTRY; // index the blk transfer table 0-7
+
+typedef struct TransferDesc
+{
+    long long   lpvBuff;            // address of transfer area (for 64 or 32 bit)
+    unsigned int dwLength;          // length of the area
+    TBLOCKENTRY wAreaNum;           // number of transfer area to set up
+    short       eSize;              // element size - is tohost flag for circular
+} TRANSFERDESC;
+
+typedef TRANSFERDESC*   LPTRANSFERDESC;
+
+typedef struct TransferEvent
+{
+    unsigned int dwStart;           // offset into the area
+    unsigned int dwLength;          // length of the region
+    unsigned short wAreaNum;        // the area number
+    unsigned short wFlags;          // bit 0 set for toHost
+    int iSetEvent;                  // could be dummy in LINUX
+} TRANSFEREVENT;
+
+#define MAX_TRANSFER_SIZE  0x4000       /* Maximum data bytes per IRP */
+#define MAX_AREA_LENGTH    0x100000     /* Maximum size of transfer area */
+#define MAX_TRANSAREAS 8                /* definitions for dma set up  */
+
+typedef struct TGetSelfTest
+{
+    int code;                           // self-test error code
+    int x,y;                            // additional information
+} TGET_SELFTEST;
+
+/// Debug block used for several commands. Not all fields are used for all commands.
+typedef struct TDbgBlock
+{
+    int iAddr;                          // the address in the 1401
+    int iRepeats;                       // number of repeats
+    int iWidth;                         // width in bytes 1, 2, 4
+    int iDefault;                       // default value
+    int iMask;                          // mask to apply
+    int iData;                          // data for poke, result for peek
+} TDBGBLOCK;
+
+/// Used to collect information about a circular block from the device driver
+typedef struct TCircBlock
+{
+    unsigned int nArea;                 // the area to collect information from
+    unsigned int dwOffset;              // offset into the area to the available block
+    unsigned int dwSize;                // size of the area
+} TCIRCBLOCK;
+
+/// Used to clollect the 1401 status
+typedef struct TCSBlock
+{
+    unsigned int uiState;
+    unsigned int uiError;
+} TCSBLOCK;
+
+// As seen by the user, an ioctl call looks like:
+// int ioctl(int fd, unsigned long cmd, char* argp);
+// We will then have all sorts of variants on this that can be used
+// to pass stuff to our driver. We will generate macros for each type
+// of call so as to provide some sort of type safety in the calling:
+#define CED_MAGIC_IOC 0xce
+
+// NBNB: READ and WRITE are from the point of view of the device, not user.
+typedef struct ced_ioc_string
+{
+    int nChars;
+    char buffer[256];
+} CED_IOC_STRING;
+
+#define  IOCTL_CED_SENDSTRING(n)        _IOC(_IOC_WRITE, CED_MAGIC_IOC, 2, n)
+
+#define  IOCTL_CED_RESET1401            _IO(CED_MAGIC_IOC, 3)
+#define  IOCTL_CED_GETCHAR              _IO(CED_MAGIC_IOC, 4)
+#define  IOCTL_CED_SENDCHAR             _IO(CED_MAGIC_IOC, 5)
+#define  IOCTL_CED_STAT1401             _IO(CED_MAGIC_IOC, 6)
+#define  IOCTL_CED_LINECOUNT            _IO(CED_MAGIC_IOC, 7)
+#define  IOCTL_CED_GETSTRING(nMax)      _IOC(_IOC_READ, CED_MAGIC_IOC, 8, nMax)
+
+#define  IOCTL_CED_SETTRANSFER          _IOW(CED_MAGIC_IOC, 11, TRANSFERDESC)
+#define  IOCTL_CED_UNSETTRANSFER        _IO(CED_MAGIC_IOC, 12)
+#define  IOCTL_CED_SETEVENT             _IOW(CED_MAGIC_IOC,13, TRANSFEREVENT)
+#define  IOCTL_CED_GETOUTBUFSPACE       _IO(CED_MAGIC_IOC, 14)
+#define  IOCTL_CED_GETBASEADDRESS       _IO(CED_MAGIC_IOC, 15)
+#define  IOCTL_CED_GETDRIVERREVISION    _IO(CED_MAGIC_IOC, 16)
+
+#define  IOCTL_CED_GETTRANSFER          _IOR(CED_MAGIC_IOC,17, TGET_TX_BLOCK)
+#define  IOCTL_CED_KILLIO1401           _IO(CED_MAGIC_IOC,18)
+#define  IOCTL_CED_BLKTRANSSTATE        _IO(CED_MAGIC_IOC,19)
+
+#define  IOCTL_CED_STATEOF1401          _IO(CED_MAGIC_IOC,23)
+#define  IOCTL_CED_GRAB1401             _IO(CED_MAGIC_IOC,25)
+#define  IOCTL_CED_FREE1401             _IO(CED_MAGIC_IOC,26)
+#define  IOCTL_CED_STARTSELFTEST        _IO(CED_MAGIC_IOC,31)
+#define  IOCTL_CED_CHECKSELFTEST        _IOR(CED_MAGIC_IOC,32, TGET_SELFTEST)
+#define  IOCTL_CED_TYPEOF1401           _IO(CED_MAGIC_IOC,33)
+#define  IOCTL_CED_TRANSFERFLAGS        _IO(CED_MAGIC_IOC,34)
+
+#define  IOCTL_CED_DBGPEEK              _IOW(CED_MAGIC_IOC,35, TDBGBLOCK)
+#define  IOCTL_CED_DBGPOKE              _IOW(CED_MAGIC_IOC,36, TDBGBLOCK)
+#define  IOCTL_CED_DBGRAMPDATA          _IOW(CED_MAGIC_IOC,37, TDBGBLOCK)
+#define  IOCTL_CED_DBGRAMPADDR          _IOW(CED_MAGIC_IOC,38, TDBGBLOCK)
+#define  IOCTL_CED_DBGGETDATA           _IOR(CED_MAGIC_IOC,39, TDBGBLOCK)
+#define  IOCTL_CED_DBGSTOPLOOP          _IO(CED_MAGIC_IOC,40)
+#define  IOCTL_CED_FULLRESET            _IO(CED_MAGIC_IOC,41)
+#define  IOCTL_CED_SETCIRCULAR          _IOW(CED_MAGIC_IOC,42, TRANSFERDESC)
+#define  IOCTL_CED_GETCIRCBLOCK         _IOWR(CED_MAGIC_IOC,43, TCIRCBLOCK)
+#define  IOCTL_CED_FREECIRCBLOCK        _IOWR(CED_MAGIC_IOC,44, TCIRCBLOCK)
+#define  IOCTL_CED_WAITEVENT            _IO(CED_MAGIC_IOC, 45)
+#define  IOCTL_CED_TESTEVENT            _IO(CED_MAGIC_IOC, 46)
+
+#ifndef __KERNEL__
+// If nothing said about return value, it is a U14ERR_... error code (U14ERR_NOERROR for none)
+inline int CED_SendString(int fh, const char* szText, int n){return ioctl(fh, IOCTL_CED_SENDSTRING(n), szText);}
+
+inline int CED_Reset1401(int fh){return ioctl(fh, IOCTL_CED_RESET1401);}
+
+inline int CED_GetChar(int fh){return ioctl(fh, IOCTL_CED_GETCHAR);}
+// Return the singe character or a -ve error code.
+
+inline int CED_Stat1401(int fh){return ioctl(fh, IOCTL_CED_STAT1401);}
+// Return character count in input buffer
+
+inline int CED_SendChar(int fh, char c){return ioctl(fh, IOCTL_CED_SENDCHAR, c);}
+
+inline int CED_LineCount(int fh){return ioctl(fh, IOCTL_CED_LINECOUNT);}
+
+inline int CED_GetString(int fh, char* szText, int nMax){return ioctl(fh, IOCTL_CED_GETSTRING(nMax), szText);}
+// return the count of characters returned. If the string was terminated by CR or 0, then the 0 is part
+// of the count. Otherwise, we will add a zero if there is room, but it is not included in the count.
+// The return value is 0 if there was nothing to read.
+
+inline int CED_GetOutBufSpace(int fh){return ioctl(fh, IOCTL_CED_GETOUTBUFSPACE);}
+// returns space in the output buffer.
+
+inline int CED_GetBaseAddress(int fh){return ioctl(fh, IOCTL_CED_GETBASEADDRESS);}
+// This always returns -1 as not implemented.
+
+inline int CED_GetDriverRevision(int fh){return ioctl(fh, IOCTL_CED_GETDRIVERREVISION);}
+// returns the major revision <<16 | minor revision.
+
+inline int CED_SetTransfer(int fh, TRANSFERDESC* pTD){return ioctl(fh, IOCTL_CED_SETTRANSFER, pTD);}
+
+inline int CED_UnsetTransfer(int fh, int nArea){return ioctl(fh, IOCTL_CED_UNSETTRANSFER, nArea);}
+
+inline int CED_SetEvent(int fh, TRANSFEREVENT* pTE){return ioctl(fh, IOCTL_CED_SETEVENT, pTE);}
+
+inline int CED_GetTransfer(int fh, TGET_TX_BLOCK* pTX){return ioctl(fh, IOCTL_CED_GETTRANSFER, pTX);}
+
+inline int CED_KillIO1401(int fh){return ioctl(fh, IOCTL_CED_KILLIO1401);}
+
+inline int CED_BlkTransState(int fh){return ioctl(fh, IOCTL_CED_BLKTRANSSTATE);}
+// returns 0 if no active DMA, 1 if active
+
+inline int CED_StateOf1401(int fh){return ioctl(fh, IOCTL_CED_STATEOF1401);}
+
+inline int CED_Grab1401(int fh){return ioctl(fh, IOCTL_CED_GRAB1401);}
+inline int CED_Free1401(int fh){return ioctl(fh, IOCTL_CED_FREE1401);}
+
+inline int CED_StartSelfTest(int fh){return ioctl(fh, IOCTL_CED_STARTSELFTEST);}
+inline int CED_CheckSelfTest(int fh, TGET_SELFTEST* pGST){return ioctl(fh, IOCTL_CED_CHECKSELFTEST, pGST);}
+
+inline int CED_TypeOf1401(int fh){return ioctl(fh, IOCTL_CED_TYPEOF1401);}
+inline int CED_TransferFlags(int fh){return ioctl(fh, IOCTL_CED_TRANSFERFLAGS);}
+
+inline int CED_DbgPeek(int fh, TDBGBLOCK* pDB){return ioctl(fh, IOCTL_CED_DBGPEEK, pDB);}
+inline int CED_DbgPoke(int fh, TDBGBLOCK* pDB){return ioctl(fh, IOCTL_CED_DBGPOKE, pDB);}
+inline int CED_DbgRampData(int fh, TDBGBLOCK* pDB){return ioctl(fh, IOCTL_CED_DBGRAMPDATA, pDB);}
+inline int CED_DbgRampAddr(int fh, TDBGBLOCK* pDB){return ioctl(fh, IOCTL_CED_DBGRAMPADDR, pDB);}
+inline int CED_DbgGetData(int fh, TDBGBLOCK* pDB){return ioctl(fh, IOCTL_CED_DBGGETDATA, pDB);}
+inline int CED_DbgStopLoop(int fh){return ioctl(fh, IOCTL_CED_DBGSTOPLOOP);}
+
+inline int CED_FullReset(int fh){return ioctl(fh, IOCTL_CED_FULLRESET);}
+
+inline int CED_SetCircular(int fh, TRANSFERDESC* pTD){return ioctl(fh, IOCTL_CED_SETCIRCULAR, pTD);}
+inline int CED_GetCircBlock(int fh, TCIRCBLOCK* pCB){return ioctl(fh, IOCTL_CED_GETCIRCBLOCK, pCB);}
+inline int CED_FreeCircBlock(int fh, TCIRCBLOCK* pCB){return ioctl(fh, IOCTL_CED_FREECIRCBLOCK, pCB);}
+
+inline int CED_WaitEvent(int fh, int nArea, int msTimeOut){return ioctl(fh, IOCTL_CED_WAITEVENT, (nArea & 0xff)|(msTimeOut << 8));}
+inline int CED_TestEvent(int fh, int nArea){return ioctl(fh, IOCTL_CED_TESTEVENT, nArea);}
+#endif
+
+#ifdef NOTWANTEDYET
+#define  IOCTL_CED_REGCALLBACK          _IO(CED_MAGIC_IOC,9)    // Not used
+#define  IOCTL_CED_GETMONITORBUF        _IO(CED_MAGIC_IOC,10)   // Not used
+
+#define  IOCTL_CED_BYTECOUNT            _IO(CED_MAGIC_IOC,20)   // Not used
+#define  IOCTL_CED_ZEROBLOCKCOUNT       _IO(CED_MAGIC_IOC,21)   // Not used
+#define  IOCTL_CED_STOPCIRCULAR         _IO(CED_MAGIC_IOC,22)   // Not used
+
+#define  IOCTL_CED_REGISTERS1401        _IO(CED_MAGIC_IOC,24)   // Not used
+#define  IOCTL_CED_STEP1401             _IO(CED_MAGIC_IOC,27)   // Not used
+#define  IOCTL_CED_SET1401REGISTERS     _IO(CED_MAGIC_IOC,28)   // Not used
+#define  IOCTL_CED_STEPTILL1401         _IO(CED_MAGIC_IOC,29)   // Not used
+#define  IOCTL_CED_SETORIN              _IO(CED_MAGIC_IOC,30)   // Not used
+
+#endif
+
+// __CED_IOCTL_H__
+#endif
diff --git a/drivers/staging/ced1401/machine.h b/drivers/staging/ced1401/machine.h
new file mode 100644 (file)
index 0000000..af07379
--- /dev/null
@@ -0,0 +1,127 @@
+/*****************************************************************************
+**
+** machine.h
+**
+** Copyright (c) Cambridge Electronic Design Limited 1991,1992,2010
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public License
+** as published by the Free Software Foundation; either version 2
+** of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program; if not, write to the Free Software
+** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+**
+** Contact CED: Cambridge Electronic Design Limited, Science Park, Milton Road
+**              Cambridge, CB6 0FE.
+**              www.ced.co.uk
+**              greg@ced.co.uk
+**
+** This file is included at the start of 'C' or 'C++' source file to define
+** things for cross-platform/compiler interoperability. This used to deal with
+** MSDOS/16-bit stuff, but this was all removed in Decemeber 2010. There are
+** three things to consider: Windows, LINUX, mac OSX (BSD Unix) and 32 vs 64
+** bit. At the time of writing (DEC 2010) there is a consensus on the following
+** and their unsigned equivalents:
+**
+** type       bits
+** char         8
+** short       16
+** int         32
+** long long   64
+**
+** long is a problem as it is always 64 bits on linux/unix and is always 32 bits
+** on windows.
+** On windows, we define _IS_WINDOWS_ and one of WIN32 or WIN64.
+** On linux we define LINUX
+** On Max OSX we define MACOSX
+**
+*/
+
+#ifndef __MACHINE_H__
+#define __MACHINE_H__
+#ifndef __KERNEL__
+#include <float.h>
+#include <limits.h>
+#endif
+
+/*
+** The initial section is to identify the operating system
+*/
+#if (defined(__linux__) || defined(_linux) || defined(__linux)) && !defined(LINUX)
+#define LINUX 1
+#endif
+
+#if (defined(__WIN32__) || defined(_WIN32)) && !defined(WIN32)
+#define WIN32 1
+#endif
+
+#if defined(__APPLE__)
+#define MACOSX
+#endif
+
+#if defined(_WIN64)
+#undef WIN32
+#undef WIN64
+#define WIN64 1
+#endif
+
+#if defined(WIN32) || defined(WIN64)
+#define _IS_WINDOWS_ 1
+#endif
+
+#if defined(LINUX) || defined(MAXOSX)
+    #define FAR
+
+    typedef int BOOL;       // To match Windows
+    typedef char * LPSTR;
+    typedef const char * LPCSTR;
+    typedef unsigned short WORD;
+    typedef unsigned int  DWORD;
+    typedef unsigned char  BYTE;
+    typedef BYTE  BOOLEAN;
+    typedef unsigned char UCHAR;
+    #define __packed __attribute__((packed))
+    typedef BYTE * LPBYTE;
+    #define HIWORD(x) (WORD)(((x)>>16) & 0xffff)
+    #define LOWORD(x) (WORD)((x) & 0xffff)
+#endif
+
+#ifdef _IS_WINDOWS_
+#include <windows.h>
+#define __packed
+#endif
+
+/*
+** Sort out the DllExport and DllImport macros. The GCC compiler has its own
+** syntax for this, though it also supports the MS specific __declspec() as
+** a synonym.
+*/
+#ifdef GNUC
+    #define DllExport __attribute__((dllexport))
+    #define DllImport __attribute__((dllimport))
+#endif
+
+#ifndef DllExport
+#ifdef _IS_WINDOWS_
+    #define DllExport __declspec(dllexport)
+    #define DllImport __declspec(dllimport)
+#else
+    #define DllExport
+    #define DllImport
+#endif
+#endif /* _IS_WINDOWS_ */
+
+    
+#ifndef TRUE
+   #define TRUE 1
+   #define FALSE 0
+#endif
+
+#endif
diff --git a/drivers/staging/ced1401/usb1401.c b/drivers/staging/ced1401/usb1401.c
new file mode 100644 (file)
index 0000000..54595b8
--- /dev/null
@@ -0,0 +1,1597 @@
+/***********************************************************************************
+ CED1401 usb driver. This basic loading is based on the usb-skeleton.c code that is:
+ Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ Copyright (C) 2012 Alois Schloegl <alois.schloegl@ist.ac.at>
+ There is not a great deal of the skeleton left.
+
+ All the remainder dealing specifically with the CED1401 is based on drivers written
+ by CED for other systems (mainly Windows) and is:
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+
+Endpoints
+*********
+There are 4 endpoints plus the control endpoint in the standard interface
+provided by most 1401s. The control endpoint is used for standard USB requests,
+plus various CED-specific transactions such as start self test, debug and get
+the 1401 status. The other endpoints are:
+
+ 1 Characters to the 1401
+ 2 Characters from the 1401
+ 3 Block data to the 1401
+ 4 Block data to the host.
+
+inside the driver these are indexed as an array from 0 to 3, transactions
+over the control endpoint are carried out using a separate mechanism. The
+use of the endpoints is mostly straightforward, with the driver issuing
+IO request packets (IRPs) as required to transfer data to and from the 1401.
+The handling of endpoint 2 is different because it is used for characters
+from the 1401, which can appear spontaneously and without any other driver
+activity - for example to repeatedly request DMA transfers in Spike2. The
+desired effect is achieved by using an interrupt endpoint which can be
+polled to see if it has data available, and writing the driver so that it
+always maintains a pending read IRP from that endpoint which will read the
+character data and terminate as soon as the 1401 makes data available. This
+works very well, some care is taken with when you kick off this character
+read IRP to avoid it being active when it is not wanted but generally it
+is running all the time.
+
+In the 2270, there are only three endpoints plus the control endpoint. In
+addition to the transactions mentioned above, the control endpoint is used
+to transfer character data to the 1401. The other endpoints are used as:
+
+ 1 Characters from the 1401
+ 2 Block data to the 1401
+ 3 Block data to the host.
+
+The type of interface available is specified by the interface subclass field
+in the interface descriptor provided by the 1401. See the USB_INT_ constants
+for the values that this field can hold.
+
+****************************************************************************
+Linux implementation
+
+Although Linux Device Drivers (3rd Edition) was a major source of information,
+it is very out of date. A lot of information was gleaned from the latest
+usb_skeleton.c code (you need to download the kernel sources to get this).
+
+To match the Windows version, everything is done using ioctl calls. All the
+device state is held in the DEVICE_EXTENSION (named to match Windows use).
+Block transfers are done by using get_user_pages() to pin down a list of
+pages that we hold a pointer to in the device driver. We also allocate a
+coherent transfer buffer of size STAGED_SZ (this must be a multiple of the
+bulk endpoint size so that the 1401 does not realise that we break large
+transfers down into smaller pieces). We use kmap_atomic() to get a kernel
+va for each page, as it is required, for copying; see CopyUserSpace().
+
+All character and data transfers are done using asynchronous IO. All Urbs are
+tracked by anchoring them. Status and debug ioctls are implemented with the
+synchronous non-Urb based transfers.
+*/
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/version.h>
+#if ( LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) )
+  #include <linux/init.h>
+  #include <linux/slab.h>
+  #include <linux/module.h>
+  #include <linux/kref.h>
+  #include <linux/uaccess.h>
+#endif
+
+
+#include "usb1401.h"
+
+/* Define these values to match your devices */
+#define USB_CED_VENDOR_ID      0x0525
+#define USB_CED_PRODUCT_ID     0xa0f0
+
+/* table of devices that work with this driver */
+static const struct usb_device_id ced_table[] =
+{
+    { USB_DEVICE(USB_CED_VENDOR_ID, USB_CED_PRODUCT_ID) },
+    { }                                        /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, ced_table);
+
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_CED_MINOR_BASE     192
+
+/* our private defines. if this grows any larger, use your own .h file */
+#define MAX_TRANSFER           (PAGE_SIZE - 512)
+/* MAX_TRANSFER is chosen so that the VM is not stressed by
+   allocations > PAGE_SIZE and the number of packets in a page
+   is an integer 512 is the largest possible packet on EHCI */
+#define WRITES_IN_FLIGHT       8
+/* arbitrarily chosen */
+
+/* 
+The cause for these errors is that the driver makes use of the functions usb_buffer_alloc() and usb_buffer_free() which got renamed in kernel 2.6.35. This is stated in the Changelog:   USB: rename usb_buffer_alloc() and usb_buffer_free() users
+    For more clearance what the functions actually do,
+      usb_buffer_alloc() is renamed to usb_alloc_coherent()
+      usb_buffer_free()  is renamed to usb_free_coherent()
+   This is needed on Debian 2.6.32-5-amd64
+*/
+#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) )
+  #define usb_alloc_coherent usb_buffer_alloc
+  #define usb_free_coherent  usb_buffer_free
+  #define noop_llseek NULL
+#endif
+
+static struct usb_driver ced_driver;
+
+static void ced_delete(struct kref *kref)
+{
+    DEVICE_EXTENSION *pdx = to_DEVICE_EXTENSION(kref);
+
+    // Free up the output buffer, then free the output urb. Note that the interface member
+    // of pdx will probably be NULL, so cannot be used to get to dev.
+    usb_free_coherent(pdx->udev, OUTBUF_SZ, pdx->pCoherCharOut, pdx->pUrbCharOut->transfer_dma);
+    usb_free_urb(pdx->pUrbCharOut);
+
+    // Do the same for chan input
+    usb_free_coherent(pdx->udev, INBUF_SZ, pdx->pCoherCharIn, pdx->pUrbCharIn->transfer_dma);
+    usb_free_urb(pdx->pUrbCharIn);
+
+    // Do the same for the block transfers
+    usb_free_coherent(pdx->udev, STAGED_SZ, pdx->pCoherStagedIO, pdx->pStagedUrb->transfer_dma);
+    usb_free_urb(pdx->pStagedUrb);
+
+    usb_put_dev(pdx->udev);
+    kfree(pdx);
+}
+
+// This is the driver end of the open() call from user space.
+static int ced_open(struct inode *inode, struct file *file)
+{
+    DEVICE_EXTENSION *pdx;
+    int retval = 0;
+    int subminor = iminor(inode);
+    struct usb_interface* interface = usb_find_interface(&ced_driver, subminor);
+    if (!interface)
+    {
+        err("%s - error, can't find device for minor %d", __func__, subminor);
+        retval = -ENODEV;
+        goto exit;
+    }
+
+    pdx = usb_get_intfdata(interface);
+    if (!pdx)
+    {
+        retval = -ENODEV;
+        goto exit;
+    }
+
+    dev_dbg(&interface->dev, "%s got pdx", __func__);
+
+    /* increment our usage count for the device */
+    kref_get(&pdx->kref);
+
+    /* lock the device to allow correctly handling errors
+     * in resumption */
+    mutex_lock(&pdx->io_mutex);
+
+    if (!pdx->open_count++)
+    {
+        retval = usb_autopm_get_interface(interface);
+        if (retval)
+        {
+            pdx->open_count--;
+            mutex_unlock(&pdx->io_mutex);
+            kref_put(&pdx->kref, ced_delete);
+            goto exit;
+        }
+    }
+    else
+    {   //uncomment this block if you want exclusive open
+        dev_err(&interface->dev, "%s fail: already open", __func__);
+               retval = -EBUSY;
+               pdx->open_count--;
+               mutex_unlock(&pdx->io_mutex);
+               kref_put(&pdx->kref, ced_delete);
+               goto exit;
+       }
+    /* prevent the device from being autosuspended */
+
+    /* save our object in the file's private structure */
+    file->private_data = pdx;
+    mutex_unlock(&pdx->io_mutex);
+
+exit:
+    return retval;
+}
+
+static int ced_release(struct inode *inode, struct file *file)
+{
+    DEVICE_EXTENSION *pdx = file->private_data;
+    if (pdx == NULL)
+        return -ENODEV;
+
+    dev_dbg(&pdx->interface->dev,"%s called", __func__);
+    mutex_lock(&pdx->io_mutex);
+    if (!--pdx->open_count && pdx->interface)   // Allow autosuspend
+        usb_autopm_put_interface(pdx->interface);
+    mutex_unlock(&pdx->io_mutex);
+
+    kref_put(&pdx->kref, ced_delete);  // decrement the count on our device
+    return 0;
+}
+
+static int ced_flush(struct file *file, fl_owner_t id)
+{
+    int res;
+    DEVICE_EXTENSION *pdx = file->private_data;
+    if (pdx == NULL)
+        return -ENODEV;
+
+    dev_dbg(&pdx->interface->dev,"%s char in pend=%d", __func__, pdx->bReadCharsPending);
+
+    /* wait for io to stop */
+    mutex_lock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev,"%s got io_mutex", __func__);
+    ced_draw_down(pdx);
+
+    /* read out errors, leave subsequent opens a clean slate */
+    spin_lock_irq(&pdx->err_lock);
+    res = pdx->errors ? (pdx->errors == -EPIPE ? -EPIPE : -EIO) : 0;
+    pdx->errors = 0;
+    spin_unlock_irq(&pdx->err_lock);
+
+    mutex_unlock(&pdx->io_mutex);
+    dev_dbg(&pdx->interface->dev,"%s exit reached", __func__);
+
+    return res;
+}
+
+
+static ssize_t ced_read(struct file *file, char *buffer, size_t count,
+                        loff_t *ppos)
+{
+    DEVICE_EXTENSION *pdx = file->private_data;
+    dev_err(&pdx->interface->dev, "%s called: use ioctl for cedusb", __func__);
+    return 0;           // as we do not do reads this way
+}
+
+static ssize_t ced_write(struct file *file, const char *user_buffer,
+                         size_t count, loff_t *ppos)
+{
+    DEVICE_EXTENSION *pdx = file->private_data;
+    dev_err(&pdx->interface->dev, "%s called: use ioctl for cedusb", __func__);
+    return 0;
+}
+
+/***************************************************************************
+** CanAcceptIoRequests
+** If the device is removed, interface is set NULL. We also clear our pointer
+** from the interface, so we should make sure that pdx is not NULL. This will
+** not help with a device extension held by a file.
+** return true if can accept new io requests, else false
+*/
+static bool CanAcceptIoRequests(DEVICE_EXTENSION* pdx)
+{
+    return pdx && pdx->interface;              // Can we accept IO requests
+}
+
+/****************************************************************************
+** Callback routine to complete writes. This may need to fire off another
+** urb to complete the transfer.
+****************************************************************************/
+static void ced_writechar_callback(struct urb* pUrb)
+{
+    DEVICE_EXTENSION *pdx = pUrb->context;
+    int nGot = pUrb->actual_length;             // what we transferred
+
+    if (pUrb->status)
+    {                                           // sync/async unlink faults aren't errors
+        if (!(pUrb->status == -ENOENT || pUrb->status == -ECONNRESET || pUrb->status == -ESHUTDOWN))
+        {
+            dev_err(&pdx->interface->dev, "%s - nonzero write bulk status received: %d", __func__, pUrb->status);
+        }
+
+        spin_lock(&pdx->err_lock);
+        pdx->errors = pUrb->status;
+        spin_unlock(&pdx->err_lock);
+        nGot = 0;                               //  and tidy up again if so
+
+        spin_lock(&pdx->charOutLock);           // already at irq level
+        pdx->dwOutBuffGet = 0;                  // Reset the output buffer
+        pdx->dwOutBuffPut = 0;
+        pdx->dwNumOutput = 0;                   // Clear the char count
+        pdx->bPipeError[0] = 1;                 // Flag an error for later
+        pdx->bSendCharsPending = false;         // Allow other threads again
+        spin_unlock(&pdx->charOutLock);         // already at irq level
+        dev_dbg(&pdx->interface->dev, "%s - char out done, 0 chars sent", __func__);
+    }
+    else
+    {
+        dev_dbg(&pdx->interface->dev, "%s - char out done, %d chars sent", __func__, nGot);
+        spin_lock(&pdx->charOutLock);           // already at irq level
+        pdx->dwNumOutput -= nGot;               // Now adjust the char send buffer
+        pdx->dwOutBuffGet += nGot;              // to match what we did
+        if (pdx->dwOutBuffGet >= OUTBUF_SZ)     // Can't do this any earlier as data could be overwritten
+            pdx->dwOutBuffGet = 0;
+
+        if (pdx->dwNumOutput > 0)               // if more to be done...
+        {
+            int nPipe = 0;                      // The pipe number to use
+            int iReturn;
+            char* pDat = &pdx->outputBuffer[pdx->dwOutBuffGet];
+            unsigned int dwCount = pdx->dwNumOutput;     // maximum to send
+            if ((pdx->dwOutBuffGet+dwCount) > OUTBUF_SZ)    // does it cross buffer end?
+                dwCount = OUTBUF_SZ - pdx->dwOutBuffGet;
+            spin_unlock(&pdx->charOutLock);             // we are done with stuff that changes
+            memcpy(pdx->pCoherCharOut, pDat, dwCount);  // copy output data to the buffer
+            usb_fill_bulk_urb(pdx->pUrbCharOut, pdx->udev,
+                              usb_sndbulkpipe(pdx->udev, pdx->epAddr[0]),
+                              pdx->pCoherCharOut, dwCount, ced_writechar_callback, pdx);
+            pdx->pUrbCharOut->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+            usb_anchor_urb(pdx->pUrbCharOut, &pdx->submitted);   // in case we need to kill it
+            iReturn = usb_submit_urb(pdx->pUrbCharOut, GFP_ATOMIC);
+            dev_dbg(&pdx->interface->dev, "%s n=%d>%s<", __func__, dwCount, pDat);
+            spin_lock(&pdx->charOutLock);       // grab lock for errors
+            if (iReturn)
+            {
+                pdx->bPipeError[nPipe] = 1;     // Flag an error to be handled later
+                pdx->bSendCharsPending = false; // Allow other threads again
+                usb_unanchor_urb(pdx->pUrbCharOut);
+                dev_err(&pdx->interface->dev, "%s usb_submit_urb() returned %d", __func__, iReturn);
+          }
+        }
+        else
+            pdx->bSendCharsPending = false;     // Allow other threads again
+        spin_unlock(&pdx->charOutLock);         // already at irq level
+    }
+}
+
+/****************************************************************************
+** SendChars
+** Transmit the characters in the output buffer to the 1401. This may need
+** breaking down into multiple transfers.
+****************************************************************************/
+int SendChars(DEVICE_EXTENSION* pdx)
+{
+    int iReturn = U14ERR_NOERROR;
+
+    spin_lock_irq(&pdx->charOutLock);                       // Protect ourselves
+
+    if ((!pdx->bSendCharsPending) &&                        // Not currently sending
+        (pdx->dwNumOutput > 0) &&                           //  has characters to output
+        (CanAcceptIoRequests(pdx)))                         //  and current activity is OK
+    {
+        unsigned int dwCount = pdx->dwNumOutput;            // Get a copy of the character count
+        pdx->bSendCharsPending = true;                      // Set flag to lock out other threads
+
+        dev_dbg(&pdx->interface->dev, "Send %d chars to 1401, EP0 flag %d\n", dwCount, pdx->nPipes == 3);
+        // If we have only 3 end points we must send the characters to the 1401 using EP0.
+        if (pdx->nPipes == 3)
+        {
+            // For EP0 character transmissions to the 1401, we have to hang about until they
+            // are gone, as otherwise without more character IO activity they will never go.
+            unsigned int count = dwCount;                   // Local char counter
+            unsigned int index = 0;                         // The index into the char buffer
+
+            spin_unlock_irq(&pdx->charOutLock);             // Free spinlock as we call USBD
+
+            while ((count > 0) && (iReturn == U14ERR_NOERROR))
+            {
+                // We have to break the transfer up into 64-byte chunks because of a 2270 problem
+                int n = count > 64 ? 64 : count;            // Chars for this xfer, max of 64
+                int nSent = usb_control_msg(pdx->udev,
+                                            usb_sndctrlpipe(pdx->udev,0), // use end point 0
+                                            DB_CHARS,                   // bRequest
+                                            (H_TO_D|VENDOR|DEVREQ),     // to the device, vendor request to the device
+                                            0,0,                        // value and index are both 0
+                                            &pdx->outputBuffer[index],  // where to send from
+                                            n,                          // how much to send
+                                            1000);                      // timeout in jiffies
+                if (nSent <= 0)
+                {
+                    iReturn = nSent ? nSent : -ETIMEDOUT;   // if 0 chars says we timed out
+                    dev_err(&pdx->interface->dev, "Send %d chars by EP0 failed: %d", n, iReturn);
+                }
+                else
+                {
+                    dev_dbg(&pdx->interface->dev, "Sent %d chars by EP0", n);
+                    count -= nSent;
+                    index += nSent;
+                }
+            }
+
+            spin_lock_irq(&pdx->charOutLock);               // Protect pdx changes, released by general code
+            pdx->dwOutBuffGet = 0;                          // so reset the output buffer
+            pdx->dwOutBuffPut = 0;
+            pdx->dwNumOutput = 0;                           // and clear the buffer count
+            pdx->bSendCharsPending = false;                 // Allow other threads again
+        }
+        else
+        {   // Here for sending chars normally - we hold the spin lock
+            int nPipe = 0;                                  // The pipe number to use
+            char* pDat = &pdx->outputBuffer[pdx->dwOutBuffGet];
+
+            if ((pdx->dwOutBuffGet+dwCount) > OUTBUF_SZ)    // does it cross buffer end?
+                dwCount = OUTBUF_SZ - pdx->dwOutBuffGet;
+            spin_unlock_irq(&pdx->charOutLock);             // we are done with stuff that changes
+            memcpy(pdx->pCoherCharOut, pDat, dwCount);      // copy output data to the buffer
+            usb_fill_bulk_urb(pdx->pUrbCharOut, pdx->udev,
+                              usb_sndbulkpipe(pdx->udev, pdx->epAddr[0]),
+                              pdx->pCoherCharOut, dwCount, ced_writechar_callback, pdx);
+            pdx->pUrbCharOut->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+            usb_anchor_urb(pdx->pUrbCharOut, &pdx->submitted);
+            iReturn = usb_submit_urb(pdx->pUrbCharOut, GFP_KERNEL);
+            spin_lock_irq(&pdx->charOutLock);       // grab lock for errors
+            if (iReturn)
+            {
+                pdx->bPipeError[nPipe] = 1;         // Flag an error to be handled later
+                pdx->bSendCharsPending = false;     // Allow other threads again
+                usb_unanchor_urb(pdx->pUrbCharOut); // remove from list of active urbs
+            }
+        }
+    }
+    else
+        if (pdx->bSendCharsPending && (pdx->dwNumOutput > 0))
+            dev_dbg(&pdx->interface->dev, "SendChars bSendCharsPending:true");
+
+
+    dev_dbg(&pdx->interface->dev, "SendChars exit code: %d", iReturn);
+    spin_unlock_irq(&pdx->charOutLock);             // Now let go of the spinlock
+    return iReturn;
+}
+
+/***************************************************************************
+** CopyUserSpace
+** This moves memory between pinned down user space and the pCoherStagedIO
+** memory buffer we use for transfers. Copy n bytes in the directions that
+** is defined by pdx->StagedRead. The user space is determined by the area
+** in pdx->StagedId and the offset in pdx->StagedDone. The user
+** area may well not start on a page boundary, so allow for that.
+**
+** We have a table of physical pages that describe the area, so we can use
+** this to get a virtual address that the kernel can use.
+**
+** pdx  Is our device extension which holds all we know about the transfer.
+** n    The number of bytes to move one way or the other.
+***************************************************************************/
+static void CopyUserSpace(DEVICE_EXTENSION *pdx, int n)
+{
+    unsigned int nArea = pdx->StagedId;
+    if (nArea < MAX_TRANSAREAS)
+    {
+        TRANSAREA *pArea = &pdx->rTransDef[nArea];  // area to be used
+        unsigned int dwOffset = pdx->StagedDone + pdx->StagedOffset + pArea->dwBaseOffset;
+        char* pCoherBuf = pdx->pCoherStagedIO;      // coherent buffer
+        if (!pArea->bUsed)
+        {
+            dev_err(&pdx->interface->dev, "%s area %d unused", __func__, nArea);
+            return;
+        }
+
+        while (n)
+        {
+            int nPage = dwOffset >> PAGE_SHIFT;    // page number in table
+            if (nPage < pArea->nPages)
+            {
+                char *pvAddress = (char*)kmap_atomic(pArea->pPages[nPage], KM_IRQ0);
+                if (pvAddress)
+                {
+                    unsigned int uiPageOff = dwOffset & (PAGE_SIZE-1);   // offset into the page
+                    size_t uiXfer = PAGE_SIZE - uiPageOff;         // max to transfer on this page
+                    if (uiXfer > n)                         // limit byte count if too much
+                        uiXfer = n;                         // for the page
+                    if (pdx->StagedRead)
+                        memcpy(pvAddress+uiPageOff, pCoherBuf, uiXfer);
+                    else
+                        memcpy(pCoherBuf, pvAddress+uiPageOff, uiXfer);
+                    kunmap_atomic(pvAddress, KM_IRQ0);
+                    dwOffset += uiXfer;
+                    pCoherBuf += uiXfer;
+                    n -= uiXfer;
+                }
+                else
+                {
+                    dev_err(&pdx->interface->dev, "%s did not map page %d", __func__, nPage);
+                    return;
+                }
+
+            }
+            else
+            {
+                dev_err(&pdx->interface->dev, "%s exceeded pages %d", __func__, nPage);
+                return;
+            }
+        }
+    }
+    else
+        dev_err(&pdx->interface->dev, "%s bad area %d", __func__, nArea);
+}
+
+// Forward declarations for stuff used circularly
+static int StageChunk(DEVICE_EXTENSION *pdx);
+/***************************************************************************
+** ReadWrite_Complete
+**
+**  Completion routine for our staged read/write Irps
+*/
+static void staged_callback(struct urb* pUrb)
+{
+    DEVICE_EXTENSION *pdx = pUrb->context;
+    unsigned int nGot = pUrb->actual_length;     // what we transferred
+    bool bCancel = false;
+    bool bRestartCharInput;             // used at the end
+
+    spin_lock(&pdx->stagedLock);        // stop ReadWriteMem() action while this routine is running
+    pdx->bStagedUrbPending = false;     // clear the flag for staged IRP pending
+
+    if (pUrb->status)
+    {   // sync/async unlink faults aren't errors
+        if (!(pUrb->status == -ENOENT || pUrb->status == -ECONNRESET || pUrb->status == -ESHUTDOWN))
+        {
+            dev_err(&pdx->interface->dev, "%s - nonzero write bulk status received: %d", __func__, pUrb->status);
+        }
+        else
+            dev_info(&pdx->interface->dev, "%s - staged xfer cancelled", __func__);
+
+        spin_lock(&pdx->err_lock);
+        pdx->errors = pUrb->status;
+        spin_unlock(&pdx->err_lock);
+        nGot = 0;                               //  and tidy up again if so
+        bCancel = true;
+    }
+    else
+    {
+        dev_dbg(&pdx->interface->dev, "%s %d chars xferred", __func__, nGot);
+        if (pdx->StagedRead)                    // if reading, save to user space
+            CopyUserSpace(pdx, nGot);           // copy from buffer to user
+        if (nGot == 0)
+            dev_dbg(&pdx->interface->dev, "%s ZLP", __func__);
+    }
+
+    // Update the transfer length based on the TransferBufferLength value in the URB
+    pdx->StagedDone += nGot;
+
+    dev_dbg(&pdx->interface->dev, "%s, done %d bytes of %d", __func__, pdx->StagedDone, pdx->StagedLength);
+
+    if ((pdx->StagedDone == pdx->StagedLength) ||       // If no more to do
+        (bCancel))                                      // or this IRP was cancelled
+    {
+        TRANSAREA* pArea = &pdx->rTransDef[pdx->StagedId];    // Transfer area info
+               dev_dbg(&pdx->interface->dev, "%s transfer done, bytes %d, cancel %d", __func__, pdx->StagedDone, bCancel);
+
+        // Here is where we sort out what to do with this transfer if using a circular buffer. We have
+        //  a completed transfer that can be assumed to fit into the transfer area. We should be able to
+        //  add this to the end of a growing block or to use it to start a new block unless the code
+        //  that calculates the offset to use (in ReadWriteMem) is totally duff.
+        if ((pArea->bCircular) && (pArea->bCircToHost) && (!bCancel) &&  // Time to sort out circular buffer info?
+            (pdx->StagedRead))                          // Only for tohost transfers for now
+        {
+            if (pArea->aBlocks[1].dwSize > 0)           // If block 1 is in use we must append to it
+            {
+                if (pdx->StagedOffset == (pArea->aBlocks[1].dwOffset + pArea->aBlocks[1].dwSize))
+                {
+                    pArea->aBlocks[1].dwSize += pdx->StagedLength;
+                    dev_dbg(&pdx->interface->dev, "RWM_Complete, circ block 1 now %d bytes at %d",
+                                                   pArea->aBlocks[1].dwSize, pArea->aBlocks[1].dwOffset);
+                }
+                else
+                {
+                    // Here things have gone very, very, wrong, but I cannot see how this can actually be achieved
+                    pArea->aBlocks[1].dwOffset = pdx->StagedOffset;
+                    pArea->aBlocks[1].dwSize = pdx->StagedLength;
+                    dev_err(&pdx->interface->dev, "%s ERROR, circ block 1 re-started %d bytes at %d",
+                                                   __func__, pArea->aBlocks[1].dwSize, pArea->aBlocks[1].dwOffset);
+                }
+            }
+            else                                        // If block 1 is not used, we try to add to block 0
+            {
+                if (pArea->aBlocks[0].dwSize > 0)       // Got stored block 0 information?
+                {                                       // Must append onto the existing block 0
+                    if (pdx->StagedOffset == (pArea->aBlocks[0].dwOffset + pArea->aBlocks[0].dwSize))
+                    {
+                        pArea->aBlocks[0].dwSize += pdx->StagedLength;  // Just add this transfer in
+                        dev_dbg(&pdx->interface->dev, "RWM_Complete, circ block 0 now %d bytes at %d",
+                                                       pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset);
+                    }
+                    else                                // If it doesn't append, put into new block 1
+                    {
+                        pArea->aBlocks[1].dwOffset = pdx->StagedOffset;
+                        pArea->aBlocks[1].dwSize = pdx->StagedLength;
+                        dev_dbg(&pdx->interface->dev, "RWM_Complete, circ block 1 started %d bytes at %d",
+                                                       pArea->aBlocks[1].dwSize, pArea->aBlocks[1].dwOffset);
+                    }
+                }
+                else                                    // No info stored yet, just save in block 0
+                {
+                    pArea->aBlocks[0].dwOffset = pdx->StagedOffset;
+                    pArea->aBlocks[0].dwSize = pdx->StagedLength;
+                    dev_dbg(&pdx->interface->dev, "RWM_Complete, circ block 0 started %d bytes at %d",
+                                                   pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset);
+                }
+            }
+        }
+
+        if (!bCancel)                                   // Don't generate an event if cancelled
+        {
+            dev_dbg(&pdx->interface->dev, "RWM_Complete,  bCircular %d, bToHost %d, eStart %d, eSize %d",
+                                         pArea->bCircular, pArea->bEventToHost, pArea->dwEventSt, pArea->dwEventSz);
+            if ((pArea->dwEventSz) &&                   // Set a user-mode event...
+                (pdx->StagedRead == pArea->bEventToHost)) // ...on transfers in this direction?
+            {
+                int iWakeUp = 0;                        // assume
+                // If we have completed the right sort of DMA transfer then set the event to notify
+                //   the user code to wake up anyone that is waiting.
+                if ((pArea->bCircular) &&               // Circular areas use a simpler test
+                    (pArea->bCircToHost))               // only in supported direction
+                {                                                                              // Is total data waiting up to size limit?
+                                       unsigned int dwTotal = pArea->aBlocks[0].dwSize + pArea->aBlocks[1].dwSize;
+                    iWakeUp = (dwTotal >= pArea->dwEventSz);
+                }
+                else
+                {
+                    unsigned int transEnd = pdx->StagedOffset + pdx->StagedLength;
+                    unsigned int eventEnd = pArea->dwEventSt + pArea->dwEventSz;
+                    iWakeUp = (pdx->StagedOffset < eventEnd) && (transEnd > pArea->dwEventSt);
+                }
+
+                if (iWakeUp)
+                {
+                    dev_dbg(&pdx->interface->dev, "About to set event to notify app");
+                    wake_up_interruptible(&pArea->wqEvent); // wake up waiting processes
+                    ++pArea->iWakeUp;                   // increment wakeup count
+                }
+            }
+        }
+
+        pdx->dwDMAFlag = MODE_CHAR;                     // Switch back to char mode before ReadWriteMem call
+
+        if (!bCancel)                                   // Don't look for waiting transfer if cancelled
+        {
+            // If we have a transfer waiting, kick it off
+            if (pdx->bXFerWaiting)                      // Got a block xfer waiting?
+            {
+                int iReturn;
+                dev_info(&pdx->interface->dev, "*** RWM_Complete *** pending transfer will now be set up!!!");
+                iReturn = ReadWriteMem(pdx, !pdx->rDMAInfo.bOutWard, pdx->rDMAInfo.wIdent, pdx->rDMAInfo.dwOffset, pdx->rDMAInfo.dwSize);
+
+                if (iReturn)
+                    dev_err(&pdx->interface->dev, "RWM_Complete rw setup failed %d", iReturn);
+            }
+        }
+
+    }
+    else                                                // Here for more to do
+        StageChunk(pdx);                                // fire off the next bit
+
+    // While we hold the stagedLock, see if we should reallow character input ints
+    // Don't allow if cancelled, or if a new block has started or if there is a waiting block.
+    // This feels wrong as we should ask which spin lock protects dwDMAFlag.
+    bRestartCharInput = !bCancel && (pdx->dwDMAFlag == MODE_CHAR) && !pdx->bXFerWaiting;
+
+    spin_unlock(&pdx->stagedLock);                      // Finally release the lock again
+
+    // This is not correct as dwDMAFlag is protected by the staged lock, but it is treated
+    // in Allowi as if it were protected by the char lock. In any case, most systems will
+    // not be upset by char input during DMA... sigh. Needs sorting out.
+    if (bRestartCharInput)                              // may be out of date, but...
+        Allowi(pdx, true);                              // ...Allowi tests a lock too.
+    dev_dbg(&pdx->interface->dev, "%s done", __func__);
+}
+
+/****************************************************************************
+** StageChunk
+**
+** Generates the next chunk of data making up a staged transfer.
+**
+** The calling code must have acquired the staging spinlock before calling
+**  this function, and is responsible for releasing it. We are at callback level.
+****************************************************************************/
+static int StageChunk(DEVICE_EXTENSION *pdx)
+{
+    int iReturn = U14ERR_NOERROR;
+       unsigned int ChunkSize;
+    int nPipe = pdx->StagedRead ? 3 : 2;                // The pipe number to use for reads or writes
+    if (pdx->nPipes == 3) nPipe--;                      // Adjust for the 3-pipe case
+    if (nPipe < 0)                                      // and trap case that should never happen
+        return U14ERR_FAIL;
+
+    if (!CanAcceptIoRequests(pdx))                      // got sudden remove?
+    {
+               dev_info(&pdx->interface->dev, "%s sudden remove, giving up", __func__);
+        return U14ERR_FAIL;                                // could do with a better error
+    }
+
+    ChunkSize = (pdx->StagedLength - pdx->StagedDone);  // transfer length remaining
+    if (ChunkSize > STAGED_SZ)                          // make sure to keep legal
+               ChunkSize = STAGED_SZ;                          //  limit to max allowed
+
+    if (!pdx->StagedRead)                               // if writing...
+        CopyUserSpace(pdx, ChunkSize);                  // ...copy data into the buffer
+
+    usb_fill_bulk_urb(pdx->pStagedUrb, pdx->udev,
+                      pdx->StagedRead ? usb_rcvbulkpipe(pdx->udev, pdx->epAddr[nPipe]):
+                                        usb_sndbulkpipe(pdx->udev, pdx->epAddr[nPipe]),
+                      pdx->pCoherStagedIO, ChunkSize, staged_callback, pdx);
+    pdx->pStagedUrb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+    usb_anchor_urb(pdx->pStagedUrb, &pdx->submitted);   // in case we need to kill it
+    iReturn = usb_submit_urb(pdx->pStagedUrb, GFP_ATOMIC);
+    if (iReturn)
+    {
+        usb_unanchor_urb(pdx->pStagedUrb);              // kill it
+        pdx->bPipeError[nPipe] = 1;                     // Flag an error to be handled later
+        dev_err(&pdx->interface->dev, "%s submit urb failed, code %d", __func__, iReturn);
+    }
+    else
+        pdx->bStagedUrbPending = true;                  // Set the flag for staged URB pending
+    dev_dbg(&pdx->interface->dev, "%s done so far:%d, this size:%d", __func__, pdx->StagedDone, ChunkSize);
+
+    return iReturn;
+}
+
+/***************************************************************************
+** ReadWriteMem
+**
+** This routine is used generally for block read and write operations.
+** Breaks up a read or write in to specified sized chunks, as specified by pipe
+** information on maximum transfer size.
+**
+** Any code that calls this must be holding the stagedLock
+**
+** Arguments:
+**    DeviceObject - pointer to our FDO (Functional Device Object)
+**    Read - TRUE for read, FALSE for write. This is from POV of the driver
+**    wIdent - the transfer area number - defines memory area and more.
+**    dwOffs - the start offset within the transfer area of the start of this
+**             transfer.
+**    dwLen - the number of bytes to transfer.
+*/
+int ReadWriteMem(DEVICE_EXTENSION *pdx, bool Read, unsigned short wIdent,
+                      unsigned int dwOffs, unsigned int dwLen)
+{
+    TRANSAREA* pArea = &pdx->rTransDef[wIdent];     // Transfer area info
+
+    if (!CanAcceptIoRequests(pdx))         // Are we in a state to accept new requests?
+    {
+               dev_err(&pdx->interface->dev, "%s can't accept requests", __func__);
+        return U14ERR_FAIL;
+    }
+
+    dev_dbg(&pdx->interface->dev, "%s xfer %d bytes to %s, offset %d, area %d",
+            __func__, dwLen, Read ? "host" : "1401", dwOffs, wIdent);
+
+    // Amazingly, we can get an escape sequence back before the current staged Urb is done, so we
+    //  have to check for this situation and, if so, wait until all is OK.
+    if (pdx->bStagedUrbPending)
+    {
+        pdx->bXFerWaiting = true;                   // Flag we are waiting
+               dev_info(&pdx->interface->dev, "%s xfer is waiting, as previous staged pending", __func__);
+        return U14ERR_NOERROR;
+    }
+
+    if (dwLen == 0)                                    // allow 0-len read or write; just return success
+    {
+               dev_dbg(&pdx->interface->dev, "%s OK; zero-len read/write request", __func__);
+        return U14ERR_NOERROR;
+    }
+
+    if ((pArea->bCircular) &&                       // Circular transfer?
+        (pArea->bCircToHost) && (Read))             // In a supported direction
+    {                                               // If so, we sort out offset ourself
+        bool bWait = false;                         // Flag for transfer having to wait
+
+           dev_dbg(&pdx->interface->dev, "Circular buffers are %d at %d and %d at %d",
+              pArea->aBlocks[0].dwSize, pArea->aBlocks[0].dwOffset, pArea->aBlocks[1].dwSize, pArea->aBlocks[1].dwOffset);
+        if (pArea->aBlocks[1].dwSize > 0)           // Using the second block already?
+        {
+            dwOffs = pArea->aBlocks[1].dwOffset + pArea->aBlocks[1].dwSize; // take offset from that
+            bWait = (dwOffs + dwLen) > pArea->aBlocks[0].dwOffset;  // Wait if will overwrite block 0?
+            bWait |= (dwOffs + dwLen) > pArea->dwLength;            // or if it overflows the buffer
+        }
+        else                                        // Area 1 not in use, try to use area 0
+        {
+            if (pArea->aBlocks[0].dwSize == 0)      // Reset block 0 if not in use
+                pArea->aBlocks[0].dwOffset = 0;
+            dwOffs = pArea->aBlocks[0].dwOffset + pArea->aBlocks[0].dwSize;
+            if ((dwOffs+dwLen) > pArea->dwLength)   // Off the end of the buffer?
+            {
+                pArea->aBlocks[1].dwOffset = 0;     // Set up to use second block
+                dwOffs = 0;
+                bWait = (dwOffs + dwLen) > pArea->aBlocks[0].dwOffset;    // Wait if will overwrite block 0?
+                bWait |= (dwOffs + dwLen) > pArea->dwLength;            // or if it overflows the buffer
+            }
+        }
+
+        if (bWait)                                  // This transfer will have to wait?
+        {
+            pdx->bXFerWaiting = true;               // Flag we are waiting
+               dev_dbg(&pdx->interface->dev, "%s xfer waiting for circular buffer space", __func__);
+            return U14ERR_NOERROR;
+        }
+
+               dev_dbg(&pdx->interface->dev, "%s circular xfer, %d bytes starting at %d", __func__, dwLen, dwOffs);
+    }
+
+    // Save the parameters for the read\write transfer
+    pdx->StagedRead = Read;                         // Save the parameters for this read
+    pdx->StagedId = wIdent;                         // ID allows us to get transfer area info
+    pdx->StagedOffset = dwOffs;                     // The area within the transfer area
+    pdx->StagedLength = dwLen;
+    pdx->StagedDone = 0;                            // Initialise the byte count
+    pdx->dwDMAFlag = MODE_LINEAR;                   // Set DMA mode flag at this point
+    pdx->bXFerWaiting = false;                      // Clearly not a transfer waiting now
+
+//    KeClearEvent(&pdx->StagingDoneEvent);           // Clear the transfer done event
+    StageChunk(pdx);                                // fire off the first chunk
+
+    return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+**
+** ReadChar
+**
+** Reads a character a buffer. If there is no more
+**  data we return FALSE. Used as part of decoding a DMA request.
+**
+****************************************************************************/
+static bool ReadChar(unsigned char* pChar, char* pBuf, unsigned int* pdDone, unsigned int dGot)
+{
+    bool bRead = false;
+    unsigned int dDone = *pdDone;
+
+    if (dDone < dGot)                       // If there is more data
+    {
+        *pChar = (unsigned char)pBuf[dDone];// Extract the next char
+        dDone++;                            // Increment the done count
+        *pdDone = dDone;
+        bRead = true;                       // and flag success
+    }
+
+    return bRead;
+}
+
+#ifdef NOTUSED
+/****************************************************************************
+**
+** ReadWord
+**
+** Reads a word from the 1401, just uses ReadChar twice; passes on any error
+**
+*****************************************************************************/
+static bool ReadWord(unsigned short* pWord, char* pBuf, unsigned int* pdDone, unsigned int dGot)
+{
+    if (ReadChar((unsigned char*)pWord, pBuf, pdDone, dGot))
+        return ReadChar(((unsigned char*)pWord)+1, pBuf, pdDone, dGot);
+    else
+        return false;
+}
+#endif
+
+/****************************************************************************
+** ReadHuff
+**
+** Reads a coded number in and returns it, Code is:
+** If data is in range 0..127 we recieve 1 byte. If data in range 128-16383
+** we recieve two bytes, top bit of first indicates another on its way. If
+** data in range 16383-4194303 we get three bytes, top two bits of first set
+** to indicate three byte total.
+**
+*****************************************************************************/
+static bool ReadHuff(volatile unsigned int* pDWord, char* pBuf, unsigned int* pdDone, unsigned int dGot)
+{
+    unsigned char ucData;                /* for each read to ReadChar */
+    bool bReturn = true;                 /* assume we will succeed */
+    unsigned int dwData = 0;             /* Accumulator for the data */
+
+    if (ReadChar(&ucData, pBuf, pdDone, dGot))
+    {
+        dwData = ucData;                        /* copy the data */
+        if ((dwData & 0x00000080) != 0)         /* Bit set for more data ? */
+        {
+            dwData &= 0x0000007F;               /* Clear the relevant bit */
+            if (ReadChar(&ucData, pBuf, pdDone, dGot))
+            {
+                dwData = (dwData << 8) | ucData;
+                if ((dwData & 0x00004000) != 0) /* three byte sequence ? */
+                {
+                    dwData &= 0x00003FFF;       /* Clear the relevant bit */
+                    if (ReadChar(&ucData, pBuf, pdDone, dGot))
+                        dwData = (dwData << 8) | ucData;
+                    else
+                        bReturn = false;
+                }
+            }
+            else
+                bReturn = false;                /* couldn't read data */
+        }
+    }
+    else
+        bReturn = false;
+
+    *pDWord = dwData;                           /* return the data */
+    return bReturn;
+}
+
+/***************************************************************************
+**
+** ReadDMAInfo
+**
+** Tries to read info about the dma request from the 1401 and decode it into
+** the dma descriptor block. We have at this point had the escape character
+** from the 1401 and now we must read in the rest of the information about
+** the transfer request. Returns FALSE if 1401 fails to respond or obselete
+** code from 1401 or bad parameters.
+**
+** The pBuf char pointer does not include the initial escape character, so
+**  we start handling the data at offset zero.
+**
+*****************************************************************************/
+static bool ReadDMAInfo(volatile DMADESC* pDmaDesc, DEVICE_EXTENSION *pdx,
+                           char* pBuf, unsigned int dwCount)
+{
+    bool bResult = false;       // assume we won't succeed
+    unsigned char ucData;
+    unsigned int dDone = 0;     // We haven't parsed anything so far
+
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    if (ReadChar(&ucData, pBuf, &dDone, dwCount))
+    {
+        unsigned char ucTransCode = (ucData & 0x0F);    // get code for transfer type
+        unsigned short wIdent = ((ucData >> 4) & 0x07); // and area identifier
+
+        // fill in the structure we were given
+        pDmaDesc->wTransType = ucTransCode; // type of transfer
+        pDmaDesc->wIdent = wIdent;          // area to use
+        pDmaDesc->dwSize = 0;               // initialise other bits
+        pDmaDesc->dwOffset = 0;
+
+        dev_dbg(&pdx->interface->dev, "%s type: %d ident: %d", __func__, pDmaDesc->wTransType, pDmaDesc->wIdent);
+
+        pDmaDesc->bOutWard = (ucTransCode != TM_EXTTOHOST); // set transfer direction
+
+        switch (ucTransCode)
+        {
+            case TM_EXTTOHOST:              // Extended linear transfer modes (the only ones!)
+            case TM_EXTTO1401:
+            {
+                bResult = ReadHuff(&(pDmaDesc->dwOffset), pBuf, &dDone, dwCount) &&
+                          ReadHuff(&(pDmaDesc->dwSize), pBuf, &dDone, dwCount);
+                if (bResult)
+                {
+                    dev_dbg(&pdx->interface->dev, "%s xfer offset & size %d %d",
+                            __func__, pDmaDesc->dwOffset, pDmaDesc->dwSize);
+
+                    if ((wIdent >= MAX_TRANSAREAS) ||       // Illegal area number, or...
+                        (!pdx->rTransDef[wIdent].bUsed) ||  // area not set up, or...
+                        (pDmaDesc->dwOffset > pdx->rTransDef[wIdent].dwLength) || // range/size
+                        ((pDmaDesc->dwOffset + pDmaDesc->dwSize) > (pdx->rTransDef[wIdent].dwLength)))
+                    {
+                        bResult = false;                    // bad parameter(s)
+                        dev_dbg(&pdx->interface->dev, "%s bad param - id %d, bUsed %d, offset %d, size %d, area length %d",
+                                 __func__, wIdent, pdx->rTransDef[wIdent].bUsed, pDmaDesc->dwOffset, pDmaDesc->dwSize,
+                                  pdx->rTransDef[wIdent].dwLength);
+                    }
+                }
+                break;
+            }
+            default:
+                break;
+        }
+    }
+    else
+        bResult = false;
+
+    if (!bResult)                            // now check parameters for validity
+        dev_err(&pdx->interface->dev, "%s error reading Esc sequence", __func__);
+
+    return bResult;
+}
+
+/****************************************************************************
+**
+** Handle1401Esc
+**
+** Deals with an escape sequence coming from the 1401. This can either be
+**  a DMA transfer request of various types or a response to an escape sequence
+**  sent to the 1401. This is called from a callback.
+**
+** Parameters are
+**
+** dwCount - the number of characters in the device extension char in buffer,
+**           this is known to be at least 2 or we will not be called.
+**
+****************************************************************************/
+static int Handle1401Esc(DEVICE_EXTENSION* pdx, char* pCh, unsigned int dwCount)
+{
+    int iReturn = U14ERR_FAIL;
+
+    // I have no idea what this next test is about. '?' is 0x3f, which is area 3, code
+    // 15. At the moment, this is not used, so it does no harm, but unless someone can
+    // tell me what this is for, it should be removed from this and the Windows driver.
+    if (pCh[0] == '?')                              // Is this an information response
+    {                                               // Parse and save the information
+    }
+    else
+    {
+        spin_lock(&pdx->stagedLock);                // Lock others out
+
+        if (ReadDMAInfo(&pdx->rDMAInfo, pdx, pCh, dwCount))    // Get DMA parameters
+        {
+            unsigned short wTransType = pdx->rDMAInfo.wTransType;    // check transfer type
+
+            dev_dbg(&pdx->interface->dev, "%s xfer to %s, offset %d, length %d", __func__,
+                    pdx->rDMAInfo.bOutWard ? "1401" : "host",
+                    pdx->rDMAInfo.dwOffset, pdx->rDMAInfo.dwSize);
+
+            if (pdx->bXFerWaiting)                  // Check here for badly out of kilter...
+            {                                       // This can never happen, really
+                dev_err(&pdx->interface->dev, "ERROR: DMA setup while transfer still waiting");
+                spin_unlock(&pdx->stagedLock);
+            }
+            else
+            {
+                if ((wTransType == TM_EXTTOHOST) || (wTransType == TM_EXTTO1401))
+                {
+                    iReturn = ReadWriteMem(pdx, !pdx->rDMAInfo.bOutWard, pdx->rDMAInfo.wIdent, pdx->rDMAInfo.dwOffset, pdx->rDMAInfo.dwSize);
+                    if (iReturn != U14ERR_NOERROR)
+                        dev_err(&pdx->interface->dev, "%s ReadWriteMem() failed %d", __func__, iReturn);
+                }
+                else                                    // This covers non-linear transfer setup
+                    dev_err(&pdx->interface->dev, "%s Unknown block xfer type %d", __func__, wTransType);
+            }
+        }
+        else                                        // Failed to read parameters
+            dev_err(&pdx->interface->dev, "%s ReadDMAInfo() fail", __func__);
+
+        spin_unlock(&pdx->stagedLock);   // OK here
+    }
+
+    dev_dbg(&pdx->interface->dev, "%s returns %d", __func__, iReturn);
+
+    return iReturn;
+}
+
+/****************************************************************************
+** Callback for the character read complete or error
+****************************************************************************/
+static void ced_readchar_callback(struct urb* pUrb)
+{
+    DEVICE_EXTENSION *pdx = pUrb->context;
+    int nGot = pUrb->actual_length;             // what we transferred
+
+    if (pUrb->status)                           // Do we have a problem to handle?
+    {
+        int nPipe = pdx->nPipes == 4 ? 1 : 0;   // The pipe number to use for error
+        // sync/async unlink faults aren't errors... just saying device removed or stopped
+        if (!(pUrb->status == -ENOENT || pUrb->status == -ECONNRESET || pUrb->status == -ESHUTDOWN))
+        {
+            dev_err(&pdx->interface->dev, "%s - nonzero write bulk status received: %d", __func__, pUrb->status);
+        }
+        else
+            dev_dbg(&pdx->interface->dev, "%s - 0 chars pUrb->status=%d (shutdown?)", __func__, pUrb->status);
+
+        spin_lock(&pdx->err_lock);
+        pdx->errors = pUrb->status;
+        spin_unlock(&pdx->err_lock);
+        nGot = 0;                               //  and tidy up again if so
+
+        spin_lock(&pdx->charInLock);            // already at irq level
+        pdx->bPipeError[nPipe] = 1;             // Flag an error for later
+    }
+    else
+    {
+        if ((nGot > 1) && ((pdx->pCoherCharIn[0] & 0x7f) == 0x1b))  // Esc sequence?
+        {
+            Handle1401Esc(pdx, &pdx->pCoherCharIn[1], nGot-1);      // handle it
+            spin_lock(&pdx->charInLock);            // already at irq level
+        }
+        else
+        {
+            spin_lock(&pdx->charInLock);            // already at irq level
+            if (nGot > 0)
+            {
+                unsigned int i;
+                if (nGot < INBUF_SZ)
+                {
+                    pdx->pCoherCharIn[nGot] = 0;    // tidy the string
+                    dev_dbg(&pdx->interface->dev, "%s got %d chars >%s<", __func__, nGot, pdx->pCoherCharIn);
+                }
+
+                // We know that whatever we read must fit in the input buffer
+                for (i = 0; i < nGot; i++)
+                {
+                    pdx->inputBuffer[pdx->dwInBuffPut++] = pdx->pCoherCharIn[i] & 0x7F;
+                    if (pdx->dwInBuffPut >= INBUF_SZ)
+                        pdx->dwInBuffPut = 0;
+                }
+
+                if ((pdx->dwNumInput + nGot) <= INBUF_SZ)
+                    pdx->dwNumInput += nGot;            // Adjust the buffer count accordingly
+            }
+            else
+                dev_dbg(&pdx->interface->dev, "%s read ZLP", __func__);
+        }
+    }
+
+    pdx->bReadCharsPending = false;         // No longer have a pending read
+    spin_unlock(&pdx->charInLock);          // already at irq level
+
+    Allowi(pdx, true);                      // see if we can do the next one
+}
+
+/****************************************************************************
+** Allowi
+**
+** This is used to make sure that there is always a pending input transfer so
+** we can pick up any inward transfers. This can be called in multiple contexts
+** so we use the irqsave version of the spinlock.
+****************************************************************************/
+int Allowi(DEVICE_EXTENSION* pdx, bool bInCallback)
+{
+    int iReturn = U14ERR_NOERROR;
+    unsigned long flags;
+    spin_lock_irqsave(&pdx->charInLock, flags);     // can be called in multiple contexts
+
+    // We don't want char input running while DMA is in progress as we know that this
+    //  can cause sequencing problems for the 2270. So don't. It will also allow the
+    //  ERR response to get back to the host code too early on some PCs, even if there
+    //  is no actual driver failure, so we don't allow this at all.
+    if (!pdx->bInDrawDown &&                        // stop input if
+        !pdx->bReadCharsPending &&                  // If no read request outstanding
+        (pdx->dwNumInput < (INBUF_SZ/2)) &&         //  and there is some space
+        (pdx->dwDMAFlag == MODE_CHAR) &&            //  not doing any DMA
+        (!pdx->bXFerWaiting) &&                     //  no xfer waiting to start
+        (CanAcceptIoRequests(pdx)))                 //  and activity is generally OK
+    {                                               //  then off we go
+        unsigned int nMax = INBUF_SZ-pdx->dwNumInput; // max we could read
+        int nPipe = pdx->nPipes == 4 ? 1 : 0;       // The pipe number to use
+
+        dev_dbg(&pdx->interface->dev, "%s %d chars in input buffer", __func__, pdx->dwNumInput);
+
+        usb_fill_int_urb(pdx->pUrbCharIn, pdx->udev,
+                         usb_rcvintpipe(pdx->udev, pdx->epAddr[nPipe]),
+                         pdx->pCoherCharIn, nMax, ced_readchar_callback,
+                         pdx, pdx->bInterval);
+        pdx->pUrbCharIn->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; // short xfers are OK by default
+        usb_anchor_urb(pdx->pUrbCharIn, &pdx->submitted);   // in case we need to kill it
+        iReturn = usb_submit_urb(pdx->pUrbCharIn, bInCallback ? GFP_ATOMIC : GFP_KERNEL);
+        if (iReturn)
+        {
+            usb_unanchor_urb(pdx->pUrbCharIn);  // remove from list of active Urbs
+            pdx->bPipeError[nPipe] = 1;         // Flag an error to be handled later
+            dev_err(&pdx->interface->dev,"%s submit urb failed: %d", __func__, iReturn);
+        }
+        else
+            pdx->bReadCharsPending = true;      // Flag that we are active here
+    }
+
+    spin_unlock_irqrestore(&pdx->charInLock, flags);
+
+    return iReturn;
+
+}
+
+/*****************************************************************************
+** The ioctl entry point to the driver that is used by us to talk to it.
+** inode    The device node (no longer in 3.0.0 kernels)
+** file     The file that is open, which holds our pdx pointer
+** ulArg    The argument passed in. Note that long is 64-bits in 64-bit system, i.e. it is big
+**          enough for a 64-bit pointer.
+*****************************************************************************/
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+static int ced_ioctl(struct file * file, unsigned int cmd, unsigned long ulArg)
+#else
+static int ced_ioctl(struct inode * node, struct file * file, unsigned int cmd, unsigned long ulArg)
+#endif
+{
+    int err = 0;
+    DEVICE_EXTENSION *pdx = file->private_data;
+    if (!CanAcceptIoRequests(pdx))          // check we still exist
+        return -ENODEV;
+
+    // Check that access is allowed, where is is needed. Anything that would have an indeterminate
+    // size will be checked by the specific command.
+    if (_IOC_DIR(cmd) & _IOC_READ)          // read from point of view of user...
+        err = !access_ok(VERIFY_WRITE, (void __user *)ulArg, _IOC_SIZE(cmd)); // is kernel write
+    else if (_IOC_DIR(cmd) & _IOC_WRITE)    // and write from point of view of user...
+        err = !access_ok(VERIFY_READ, (void __user *)ulArg, _IOC_SIZE(cmd)); // is kernel read
+    if (err)
+        return -EFAULT;
+
+    switch (_IOC_NR(cmd))
+    {
+    case _IOC_NR(IOCTL_CED_SENDSTRING(0)):
+        return SendString(pdx, (const char __user*)ulArg, _IOC_SIZE(cmd));
+
+    case _IOC_NR(IOCTL_CED_RESET1401):
+        return Reset1401(pdx);
+
+    case _IOC_NR(IOCTL_CED_GETCHAR):
+        return GetChar(pdx);
+
+    case _IOC_NR(IOCTL_CED_SENDCHAR):
+        return SendChar(pdx, (char)ulArg);
+
+    case _IOC_NR(IOCTL_CED_STAT1401):
+        return Stat1401(pdx);
+
+    case _IOC_NR(IOCTL_CED_LINECOUNT):
+        return LineCount(pdx);
+
+    case _IOC_NR(IOCTL_CED_GETSTRING(0)):
+        return GetString(pdx, (char __user*)ulArg, _IOC_SIZE(cmd));
+
+    case _IOC_NR(IOCTL_CED_SETTRANSFER):
+        return SetTransfer(pdx, (TRANSFERDESC __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_UNSETTRANSFER):
+        return UnsetTransfer(pdx, (int)ulArg);
+
+    case _IOC_NR(IOCTL_CED_SETEVENT):
+        return SetEvent(pdx, (TRANSFEREVENT __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_GETOUTBUFSPACE):
+        return GetOutBufSpace(pdx);
+
+    case _IOC_NR(IOCTL_CED_GETBASEADDRESS):
+        return -1;
+
+    case _IOC_NR(IOCTL_CED_GETDRIVERREVISION):
+        return (2<<24)|(DRIVERMAJREV<<16) | DRIVERMINREV;   // USB | MAJOR | MINOR
+
+    case _IOC_NR(IOCTL_CED_GETTRANSFER):
+        return GetTransfer(pdx, (TGET_TX_BLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_KILLIO1401):
+        return KillIO1401(pdx);
+
+    case _IOC_NR(IOCTL_CED_STATEOF1401):
+        return StateOf1401(pdx);
+
+    case _IOC_NR(IOCTL_CED_GRAB1401):
+    case _IOC_NR(IOCTL_CED_FREE1401):
+        return U14ERR_NOERROR;
+
+    case _IOC_NR(IOCTL_CED_STARTSELFTEST):
+        return StartSelfTest(pdx);
+
+    case _IOC_NR(IOCTL_CED_CHECKSELFTEST):
+        return CheckSelfTest(pdx, (TGET_SELFTEST __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_TYPEOF1401):
+        return TypeOf1401(pdx);
+
+    case _IOC_NR(IOCTL_CED_TRANSFERFLAGS):
+        return TransferFlags(pdx);
+
+    case _IOC_NR(IOCTL_CED_DBGPEEK):
+        return DbgPeek(pdx, (TDBGBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_DBGPOKE):
+        return DbgPoke(pdx, (TDBGBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_DBGRAMPDATA):
+        return DbgRampData(pdx, (TDBGBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_DBGRAMPADDR):
+        return DbgRampAddr(pdx, (TDBGBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_DBGGETDATA):
+        return DbgGetData(pdx, (TDBGBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_DBGSTOPLOOP):
+        return DbgStopLoop(pdx);
+
+    case _IOC_NR(IOCTL_CED_FULLRESET):
+          pdx->bForceReset = true;              // Set a flag for a full reset
+          break;
+
+    case _IOC_NR(IOCTL_CED_SETCIRCULAR):
+        return SetCircular(pdx, (TRANSFERDESC __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_GETCIRCBLOCK):
+        return GetCircBlock(pdx, (TCIRCBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_FREECIRCBLOCK):
+        return FreeCircBlock(pdx, (TCIRCBLOCK __user*)ulArg);
+
+    case _IOC_NR(IOCTL_CED_WAITEVENT):
+        return WaitEvent(pdx, (int)(ulArg & 0xff), (int)(ulArg >> 8));
+
+    case _IOC_NR(IOCTL_CED_TESTEVENT):
+        return TestEvent(pdx, (int)ulArg);
+
+    default:
+        return U14ERR_NO_SUCH_FN;
+    }
+    return U14ERR_NOERROR;
+}
+
+static const struct file_operations ced_fops =
+{
+    .owner =   THIS_MODULE,
+    .read =            ced_read,
+    .write =   ced_write,
+    .open =            ced_open,
+    .release = ced_release,
+    .flush =   ced_flush,
+    .llseek =  noop_llseek,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
+    .unlocked_ioctl =    ced_ioctl,
+#else
+    .ioctl =    ced_ioctl,
+#endif
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver ced_class =
+{
+    .name =            "cedusb%d",
+    .fops =            &ced_fops,
+    .minor_base =      USB_CED_MINOR_BASE,
+};
+
+// Check that the device that matches a 1401 vendor and product ID is OK to use and
+// initialise our DEVICE_EXTENSION.
+static int ced_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+    DEVICE_EXTENSION *pdx;
+    struct usb_host_interface *iface_desc;
+    struct usb_endpoint_descriptor *endpoint;
+    int i, bcdDevice;
+    int retval = -ENOMEM;
+
+    // allocate memory for our device extension and initialize it
+    pdx = kzalloc(sizeof(*pdx), GFP_KERNEL);
+    if (!pdx)
+    {
+        err("Out of memory");
+        goto error;
+    }
+
+    for (i=0; i<MAX_TRANSAREAS; ++i)      // Initialise the wait queues
+    {
+        init_waitqueue_head(&pdx->rTransDef[i].wqEvent);
+    }
+
+    // Put initialises for our stuff here. Note that all of *pdx is zero, so
+    // no need to explicitly zero it.
+    spin_lock_init(&pdx->charOutLock);
+    spin_lock_init(&pdx->charInLock);
+    spin_lock_init(&pdx->stagedLock);
+
+    // Initialises from the skeleton stuff
+    kref_init(&pdx->kref);
+    mutex_init(&pdx->io_mutex);
+    spin_lock_init(&pdx->err_lock);
+    init_usb_anchor(&pdx->submitted);
+
+    pdx->udev = usb_get_dev(interface_to_usbdev(interface));
+    pdx->interface = interface;
+
+    // Attempt to identify the device
+    bcdDevice = pdx->udev->descriptor.bcdDevice;
+    i = (bcdDevice >> 8);
+    if (i == 0)
+        pdx->s1401Type = TYPEU1401;
+    else if ((i>=1) && (i<=23))
+        pdx->s1401Type = i+2;
+    else
+    {
+        dev_err(&interface->dev, "%s Unknown device. bcdDevice = %d", __func__, bcdDevice);
+        goto error;
+    }
+    // set up the endpoint information. We only care about the number of EP as
+    // we know that we are dealing with a 1401 device.
+    iface_desc = interface->cur_altsetting;
+    pdx->nPipes = iface_desc->desc.bNumEndpoints;
+    dev_info(&interface->dev, "1401Type=%d with %d End Points", pdx->s1401Type, pdx->nPipes);
+    if ((pdx->nPipes < 3) || (pdx->nPipes > 4))
+        goto error;
+
+    // Allocate the URBs we hold for performing transfers
+    pdx->pUrbCharOut = usb_alloc_urb(0, GFP_KERNEL);    // character output URB
+    pdx->pUrbCharIn = usb_alloc_urb(0, GFP_KERNEL);     // character input URB
+    pdx->pStagedUrb = usb_alloc_urb(0, GFP_KERNEL);     // block transfer URB
+    if (!pdx->pUrbCharOut || !pdx->pUrbCharIn || !pdx->pStagedUrb)
+    {
+        dev_err(&interface->dev, "%s URB alloc failed", __func__);
+        goto error;
+    }
+
+    pdx->pCoherStagedIO = usb_alloc_coherent(pdx->udev, STAGED_SZ, GFP_KERNEL, &pdx->pStagedUrb->transfer_dma);
+    pdx->pCoherCharOut = usb_alloc_coherent(pdx->udev, OUTBUF_SZ, GFP_KERNEL, &pdx->pUrbCharOut->transfer_dma);
+    pdx->pCoherCharIn = usb_alloc_coherent(pdx->udev, INBUF_SZ, GFP_KERNEL, &pdx->pUrbCharIn->transfer_dma);
+    if (!pdx->pCoherCharOut || !pdx->pCoherCharIn || !pdx->pCoherStagedIO)
+    {
+        dev_err(&interface->dev, "%s Coherent buffer alloc failed", __func__);
+        goto error;
+    }
+
+    for (i = 0; i < pdx->nPipes; ++i)
+    {
+        endpoint = &iface_desc->endpoint[i].desc;
+        pdx->epAddr[i] = endpoint->bEndpointAddress;
+        dev_info(&interface->dev, "Pipe %d, ep address %02x", i, pdx->epAddr[i]);
+        if (((pdx->nPipes==3) && (i==0)) || // if char input end point
+            ((pdx->nPipes==4) && (i==1)))
+        {
+            pdx->bInterval = endpoint->bInterval;   // save the endpoint interrupt interval
+            dev_info(&interface->dev, "Pipe %d, bInterval = %d", i, pdx->bInterval);
+        }
+
+        // Detect USB2 by checking last ep size (64 if USB1)
+        if (i == pdx->nPipes-1)     // if this is the last ep (bulk)
+        {
+            pdx->bIsUSB2 = le16_to_cpu(endpoint->wMaxPacketSize) > 64;
+            dev_info(&pdx->interface->dev, "USB%d", pdx->bIsUSB2 + 1);
+        }
+    }
+
+    /* save our data pointer in this interface device */
+    usb_set_intfdata(interface, pdx);
+
+    /* we can register the device now, as it is ready */
+    retval = usb_register_dev(interface, &ced_class);
+    if (retval)
+    {
+        /* something prevented us from registering this driver */
+        err("Not able to get a minor for this device.");
+        usb_set_intfdata(interface, NULL);
+        goto error;
+    }
+
+    /* let the user know what node this device is now attached to */
+    dev_info(&interface->dev,
+             "USB CEDUSB device now attached to cedusb #%d",
+             interface->minor);
+    return 0;
+
+error:
+    if (pdx)
+        kref_put(&pdx->kref, ced_delete);   // frees allocated memory
+    return retval;
+}
+
+static void ced_disconnect(struct usb_interface *interface)
+{
+    DEVICE_EXTENSION *pdx = usb_get_intfdata(interface);
+    int minor = interface->minor;               // save for message at the end
+    int i;
+
+    usb_set_intfdata(interface, NULL);          // remove the pdx from the interface
+    usb_deregister_dev(interface, &ced_class);  // give back our minor device number
+
+    mutex_lock(&pdx->io_mutex);                 // stop more I/O starting while...
+    ced_draw_down(pdx);                         // ...wait for then kill any io
+    for (i=0; i<MAX_TRANSAREAS; ++i)
+    {
+        int iErr = ClearArea(pdx, i);           // ...release any used memory
+        if (iErr == U14ERR_UNLOCKFAIL)
+            dev_err(&pdx->interface->dev, "%s Area %d was in used", __func__, i);
+    }
+    pdx->interface = NULL;                      // ...we kill off link to interface
+    mutex_unlock(&pdx->io_mutex);
+
+    usb_kill_anchored_urbs(&pdx->submitted);
+
+    kref_put(&pdx->kref, ced_delete);           // decrement our usage count
+
+    dev_info(&interface->dev, "USB cedusb #%d now disconnected", minor);
+}
+
+// Wait for all the urbs we know of to be done with, then kill off any that
+// are left. NBNB we will need to have a mechanism to stop circular xfers
+// from trying to fire off more urbs. We will wait up to 3 seconds for Urbs
+// to be done.
+void ced_draw_down(DEVICE_EXTENSION *pdx)
+{
+    int time;
+    dev_dbg(&pdx->interface->dev,"%s called", __func__);
+
+    pdx->bInDrawDown = true;
+    time = usb_wait_anchor_empty_timeout(&pdx->submitted, 3000);
+    if (!time)              // if we timed out we kill the urbs
+    {
+        usb_kill_anchored_urbs(&pdx->submitted);
+        dev_err(&pdx->interface->dev,"%s timed out", __func__);
+    }
+    pdx->bInDrawDown = false;
+ }
+
+static int ced_suspend(struct usb_interface *intf, pm_message_t message)
+{
+    DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+    if (!pdx)
+        return 0;
+    ced_draw_down(pdx);
+
+    dev_dbg(&pdx->interface->dev,"%s called", __func__);
+    return 0;
+}
+
+static int ced_resume(struct usb_interface *intf)
+{
+    DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+    if (!pdx)
+        return 0;
+    dev_dbg(&pdx->interface->dev,"%s called", __func__);
+    return 0;
+}
+
+static int ced_pre_reset(struct usb_interface *intf)
+{
+    DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+    mutex_lock(&pdx->io_mutex);
+    ced_draw_down(pdx);
+    return 0;
+}
+
+static int ced_post_reset(struct usb_interface *intf)
+{
+    DEVICE_EXTENSION *pdx = usb_get_intfdata(intf);
+    dev_dbg(&pdx->interface->dev, "%s", __func__);
+
+    /* we are sure no URBs are active - no locking needed */
+    pdx->errors = -EPIPE;
+    mutex_unlock(&pdx->io_mutex);
+
+    return 0;
+}
+
+static struct usb_driver ced_driver =
+{
+    .name =                "cedusb",
+    .probe =       ced_probe,
+    .disconnect =      ced_disconnect,
+    .suspend =     ced_suspend,
+    .resume =      ced_resume,
+    .pre_reset =       ced_pre_reset,
+    .post_reset =      ced_post_reset,
+    .id_table =            ced_table,
+    .supports_autosuspend = 1,
+};
+
+static int __init usb_skel_init(void)
+{
+    /* register this driver with the USB subsystem */
+    int result = usb_register(&ced_driver);
+    if (result)
+        err("usb_register failed. Error number %d", result);
+
+    return result;
+}
+
+static void __exit usb_skel_exit(void)
+{
+    /* deregister this driver with the USB subsystem */
+    usb_deregister(&ced_driver);
+}
+
+module_init(usb_skel_init);
+module_exit(usb_skel_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/ced1401/usb1401.h b/drivers/staging/ced1401/usb1401.h
new file mode 100644 (file)
index 0000000..331ca98
--- /dev/null
@@ -0,0 +1,249 @@
+/* usb1401.h
+ Header file for the CED 1401 USB device driver for Linux
+ Copyright (C) 2010 Cambridge Electronic Design Ltd
+ Author Greg P Smith (greg@ced.co.uk)
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+#ifndef __USB1401_H__
+#define __USB1401_H__
+#include "use1401.h"
+#include "ced_ioctl.h"
+
+#ifndef UINT
+#define UINT unsigned int
+#endif
+
+/// Device type codes, but these don't need to be extended - a succession is assumed
+/// These are set for usb from the bcdDevice field (suitably mangled). Future devices
+/// will be added in order of device creation to the list, so the names here are just
+/// to help use remember which device is which. The U14ERR_... values follow the same
+/// pattern for modern devices.
+#define TYPEUNKNOWN        -1             // dont know
+#define TYPE1401           0              // standard 1401
+#define TYPEPLUS           1              // 1401 plus
+#define TYPEU1401          2              // u1401
+#define TYPEPOWER          3              // Power1401
+#define TYPEU14012         4              // u1401 mkII
+#define TYPEPOWER2         5              // Power1401 mk II
+#define TYPEMICRO3         6              // Micro1401-3
+#define TYPEPOWER3         7              // Power1401-3
+
+/// Some useful defines of constants. DONT FORGET to change the version in the
+/// resources whenever you change it here!.
+#define DRIVERMAJREV      2             // driver revision level major (match windows)
+#define DRIVERMINREV      0             // driver revision level minor
+
+/// Definitions of the various block transfer command codes
+#define TM_EXTTOHOST    8               // extended tohost
+#define TM_EXTTO1401    9               // extended to1401
+
+/// Definitions of values in usbReqtype. Used in sorting out setup actions
+#define H_TO_D 0x00
+#define D_TO_H 0x80
+#define VENDOR 0x40
+#define DEVREQ 0x00
+#define INTREQ 0x01
+#define ENDREQ 0x02
+
+/// Definition of values in usbRequest, again used to sort out setup
+#define GET_STATUS      0x00
+#define CLEAR_FEATURE   0x01
+#define SET_FEATURE     0x03
+#define SET_ADDRESS     0x05
+#define GET_DESC        0x06
+#define SET_DESC        0x07
+#define GET_CONF        0x08
+#define SET_CONF        0x09
+#define GET_INTERFACE   0x0a
+#define SET_INTERFACE   0x0b
+#define SYNCH_FRAME     0x0c
+
+/// Definitions of the various debug command codes understood by the 1401. These
+/// are used in various vendor-specific commands to achieve the desired effect
+#define DB_GRAB         0x50            /* Grab is a NOP for USB */
+#define DB_FREE         0x51            /* Free is a NOP for the USB */
+#define DB_SETADD       0x52            /* Set debug address (double) */
+#define DB_SELFTEST     0x53            /* Start self test */
+#define DB_SETMASK      0x54            /* Set enable mask (double) */
+#define DB_SETDEF       0x55            /* Set default mask (double) */
+#define DB_PEEK         0x56            /* Peek address, save result */
+#define DB_POKE         0x57            /* Poke address with data (double) */
+#define DB_RAMPD        0x58            /* Ramp data at debug address */
+#define DB_RAMPA        0x59            /* Ramp address bus */
+#define DB_REPEATS      0x5A            /* Set repeats for operations (double) */
+#define DB_WIDTH        0x5B            /* Set width for operations (byte) */
+#define DB_DATA         0x5C            /* Get 4-byte data read by PEEK */
+#define DB_CHARS        0x5D            /* Send chars via EP0 control write */
+
+#define CR_CHAR          0x0D           /* The carriage return character */
+#define CR_CHAR_80       0x8d           /*  and with bit 7 set */
+
+/// A structure holding information about a block of memory for use in circular transfers
+typedef struct circBlk
+{
+    volatile UINT dwOffset;             /* Offset within area of block start */
+    volatile UINT dwSize;               /* Size of the block, in bytes (0 = unused) */
+} CIRCBLK;
+
+/// A structure holding all of the information about a transfer area - an area of
+///  memory set up for use either as a source or destination in DMA transfers.
+typedef struct transarea
+{
+    void*       lpvBuff;                // User address of xfer area saved for completeness
+    UINT        dwBaseOffset;           // offset to start of xfer area in first page
+    UINT        dwLength;               // Length of xfer area, in bytes
+    struct page **pPages;               // Points at array of locked down pages
+    int         nPages;                 // number of pages that are locked down
+    bool        bUsed;                  // Is this structure in use?
+    bool        bCircular;              // Is this area for circular transfers?
+    bool        bCircToHost;            // Flag for direction of circular transfer
+    bool        bEventToHost;           // Set event on transfer to host?
+    int         iWakeUp;                // Set 1 on event, cleared by TestEvent()
+    UINT        dwEventSt;              // Defines section within xfer area for...
+    UINT        dwEventSz;              // ...notification by the event SZ is 0 if unset
+    CIRCBLK     aBlocks[2];             // Info on a pair of circular blocks
+    wait_queue_head_t wqEvent;          // The wait queue for events in this area MUST BE LAST
+} TRANSAREA;
+
+/// The DMADESC structure is used to hold information on the transfer in progress. It
+/// is set up by ReadDMAInfo, using information sent by the 1401 in an escape sequence.
+typedef struct dmadesc
+{
+    unsigned short wTransType;          /* transfer type as TM_xxx above        */
+    unsigned short wIdent;              /* identifier word                      */
+    unsigned int   dwSize;              /* bytes to transfer                    */
+    unsigned int   dwOffset;            /* offset into transfer area for trans  */
+    bool           bOutWard;            /* true when data is going TO 1401      */
+} DMADESC;
+
+#define INBUF_SZ         256            /* input buffer size */
+#define OUTBUF_SZ        256            /* output buffer size */
+#define STAGED_SZ 0x10000               // size of coherent buffer for staged transfers
+
+/// Structure to hold all of our device specific stuff. We are making this as similar as we
+/// can to the Windows driver to help in our understanding of what is going on.
+typedef struct _DEVICE_EXTENSION
+{
+    char inputBuffer[INBUF_SZ];         /* The two buffers */
+    char outputBuffer[OUTBUF_SZ];       /* accessed by the host functions */
+    volatile unsigned int dwNumInput;   /* num of chars in input buffer   */
+    volatile unsigned int dwInBuffGet;  /* where to get from input buffer */
+    volatile unsigned int dwInBuffPut;  /* where to put into input buffer */
+    volatile unsigned int dwNumOutput;  /* num of chars in output buffer  */
+    volatile unsigned int dwOutBuffGet; /* where to get from output buffer*/
+    volatile unsigned int dwOutBuffPut; /* where to put into output buffer*/
+
+    volatile bool bSendCharsPending;    /* Flag to indicate sendchar active */
+    volatile bool bReadCharsPending;    /* Flag to indicate a read is primed */
+    char* pCoherCharOut;                /* special aligned buffer for chars to 1401 */
+    struct urb* pUrbCharOut;            /* urb used for chars to 1401 */
+    char* pCoherCharIn;                 /* special aligned buffer for chars to host */
+    struct urb* pUrbCharIn;             /* urb used for chars to host */
+
+    spinlock_t charOutLock;             /* to protect the outputBuffer and outputting */
+    spinlock_t charInLock;              /* to protect the inputBuffer and char reads */
+    __u8 bInterval;                     /* Interrupt end point interval */
+
+    volatile unsigned int dwDMAFlag;    /* state of DMA */
+    TRANSAREA rTransDef[MAX_TRANSAREAS];/* transfer area info */
+    volatile DMADESC rDMAInfo;          // info on current DMA transfer
+    volatile bool bXFerWaiting;         // Flag set if DMA transfer stalled
+    volatile bool bInDrawDown;          // Flag that we want to halt transfers
+
+    // Parameters relating to a block read\write that is in progress. Some of these values
+    //  are equivalent to values in rDMAInfo. The values here are those in use, while those
+    //  in rDMAInfo are those recieved from the 1401 via an escape sequence. If another
+    //  escape sequence arrives before the previous xfer ends, rDMAInfo values are updated while these
+    //  are used to finish off the current transfer.
+    volatile short StagedId;            // The transfer area id for this transfer
+    volatile bool StagedRead;           // Flag TRUE for read from 1401, FALSE for write
+    volatile unsigned int StagedLength; // Total length of this transfer
+    volatile unsigned int StagedOffset; // Offset within memory area for transfer start
+    volatile unsigned int StagedDone;   // Bytes transferred so far
+    volatile bool bStagedUrbPending;    // Flag to indicate active
+    char* pCoherStagedIO;               // buffer used for block transfers
+    struct urb* pStagedUrb;             // The URB to use
+    spinlock_t stagedLock;              // protects ReadWriteMem() and circular buffer stuff
+
+    short s1401Type;                    // type of 1401 attached
+    short sCurrentState;                // current error state
+    bool bIsUSB2;                       // type of the interface we connect to
+    bool bForceReset;                   // Flag to make sure we get a real reset
+    __u32 statBuf[2];                   // buffer for 1401 state info
+
+    unsigned long ulSelfTestTime;       // used to timeout self test
+
+    int nPipes;                         // Should be 3 or 4 depending on 1401 usb chip
+    int bPipeError[4];                  // set non-zero if an error on one of the pipe
+    __u8 epAddr[4];                     // addresses of the 3/4 end points
+
+    struct usb_device *udev;            // the usb device for this device
+    struct usb_interface *interface;    // the interface for this device, NULL if removed
+    struct usb_anchor submitted;        // in case we need to retract our submissions
+    struct mutex io_mutex;              // synchronize I/O with disconnect, one user-mode caller at a time
+
+    int    errors;                      // the last request tanked
+    int    open_count;                  // count the number of openers
+    spinlock_t err_lock;                // lock for errors
+    struct kref kref;
+}DEVICE_EXTENSION, *PDEVICE_EXTENSION;
+#define to_DEVICE_EXTENSION(d) container_of(d, DEVICE_EXTENSION, kref)
+
+/// Definitions of routimes used between compilation object files
+// in usb1401.c
+extern int Allowi(DEVICE_EXTENSION* pdx, bool bInCallback);
+extern int SendChars(DEVICE_EXTENSION* pdx);
+extern void ced_draw_down(DEVICE_EXTENSION *pdx);
+extern int ReadWriteMem(DEVICE_EXTENSION *pdx, bool Read, unsigned short wIdent,
+                      unsigned int dwOffs, unsigned int dwLen);
+
+// in ced_ioc.c
+extern int ClearArea(DEVICE_EXTENSION *pdx, int nArea);
+extern int SendString(DEVICE_EXTENSION* pdx, const char __user* pData, unsigned int n);
+extern int SendChar(DEVICE_EXTENSION *pdx, char c);
+extern int Get1401State(DEVICE_EXTENSION* pdx, __u32* state, __u32* error);
+extern int ReadWrite_Cancel(DEVICE_EXTENSION *pdx);
+extern bool Is1401(DEVICE_EXTENSION* pdx);
+extern bool QuickCheck(DEVICE_EXTENSION* pdx, bool bTestBuff, bool bCanReset);
+extern int Reset1401(DEVICE_EXTENSION *pdx);
+extern int GetChar(DEVICE_EXTENSION *pdx);
+extern int GetString(DEVICE_EXTENSION *pdx, char __user* pUser, int n);
+extern int SetTransfer(DEVICE_EXTENSION *pdx, TRANSFERDESC __user *pTD);
+extern int UnsetTransfer(DEVICE_EXTENSION *pdx, int nArea);
+extern int SetEvent(DEVICE_EXTENSION *pdx, TRANSFEREVENT __user*pTE);
+extern int Stat1401(DEVICE_EXTENSION *pdx);
+extern int LineCount(DEVICE_EXTENSION *pdx);
+extern int GetOutBufSpace(DEVICE_EXTENSION *pdx);
+extern int GetTransfer(DEVICE_EXTENSION *pdx, TGET_TX_BLOCK __user *pGTB);
+extern int KillIO1401(DEVICE_EXTENSION *pdx);
+extern int BlkTransState(DEVICE_EXTENSION *pdx);
+extern int StateOf1401(DEVICE_EXTENSION *pdx);
+extern int StartSelfTest(DEVICE_EXTENSION *pdx);
+extern int CheckSelfTest(DEVICE_EXTENSION *pdx, TGET_SELFTEST __user *pGST);
+extern int TypeOf1401(DEVICE_EXTENSION *pdx);
+extern int TransferFlags(DEVICE_EXTENSION *pdx);
+extern int DbgPeek(DEVICE_EXTENSION *pdx, TDBGBLOCK __user* pDB);
+extern int DbgPoke(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgRampData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgRampAddr(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgGetData(DEVICE_EXTENSION *pdx, TDBGBLOCK __user *pDB);
+extern int DbgStopLoop(DEVICE_EXTENSION *pdx);
+extern int SetCircular(DEVICE_EXTENSION *pdx, TRANSFERDESC __user *pTD);
+extern int GetCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user* pCB);
+extern int FreeCircBlock(DEVICE_EXTENSION *pdx, TCIRCBLOCK __user* pCB);
+extern int WaitEvent(DEVICE_EXTENSION *pdx, int nArea, int msTimeOut);
+extern int TestEvent(DEVICE_EXTENSION *pdx, int nArea);
+#endif
diff --git a/drivers/staging/ced1401/use1401.h b/drivers/staging/ced1401/use1401.h
new file mode 100644 (file)
index 0000000..86294e2
--- /dev/null
@@ -0,0 +1,287 @@
+/****************************************************************************
+** use1401.h
+** Copyright (C) Cambridge Electronic Design Ltd, 1992-2010
+** Authors: Paul Cox, Tim Bergel, Greg Smith
+** See CVS for revisions.
+**
+** Because the size of a long is different between 32-bit and 64-bit on some
+** systems, we avoid this in this interface.
+****************************************************************************/
+#ifndef __USE1401_H__
+#define __USE1401_H__
+#include "machine.h"
+
+// Some definitions to make things compatible. If you want to use Use1401 directly
+//  from a Windows program you should define U14_NOT_DLL, in which case you also
+//  MUST make sure that your application startup code calls U14InitLib().
+// DLL_USE1401 is defined when you are building the Use1401 dll, not otherwise.
+#ifdef _IS_WINDOWS_
+#ifndef U14_NOT_DLL
+#ifdef DLL_USE1401
+#define U14API(retType) retType DllExport __stdcall
+#else
+#define U14API(retType) retType DllImport __stdcall
+#endif
+#endif
+
+#define U14ERRBASE -500
+#define U14LONG long
+#endif
+
+#ifdef LINUX
+#define U14ERRBASE -1000
+#define U14LONG int
+#endif
+
+#ifdef _QT
+#ifndef U14_NOT_DLL
+#undef U14API
+#define U14API(retType) retType __declspec(dllimport) __stdcall
+#endif
+#undef U14LONG
+#define U14LONG int
+#endif
+
+#ifndef U14API
+#define U14API(retType) retType
+#endif
+
+#ifndef U14LONG
+#define U14LONG long
+#endif
+
+/// Error codes: We need them here as user space can see them.
+#define U14ERR_NOERROR        0             // no problems
+
+/// Device error codes, but these don't need to be extended - a succession is assumed
+#define U14ERR_STD            4              // standard 1401 connected
+#define U14ERR_U1401          5              // u1401 connected
+#define U14ERR_PLUS           6              // 1401 plus connected
+#define U14ERR_POWER          7              // Power1401 connected
+#define U14ERR_U14012         8              // u1401 mkII connected
+#define U14ERR_POWER2         9
+#define U14ERR_U14013        10
+#define U14ERR_POWER3        11
+
+/// NBNB Error numbers need shifting as some linux error codes start at 512
+#define U14ERR(n)             (n+U14ERRBASE)
+#define U14ERR_OFF            U14ERR(0)      /* 1401 there but switched off    */
+#define U14ERR_NC             U14ERR(-1)     /* 1401 not connected             */
+#define U14ERR_ILL            U14ERR(-2)     /* if present it is ill           */
+#define U14ERR_NOIF           U14ERR(-3)     /* I/F card missing               */
+#define U14ERR_TIME           U14ERR(-4)     /* 1401 failed to come ready      */
+#define U14ERR_BADSW          U14ERR(-5)     /* I/F card bad switches          */
+#define U14ERR_PTIME          U14ERR(-6)     /* 1401plus failed to come ready  */
+#define U14ERR_NOINT          U14ERR(-7)     /* couldn't grab the int vector   */
+#define U14ERR_INUSE          U14ERR(-8)     /* 1401 is already in use         */
+#define U14ERR_NODMA          U14ERR(-9)     /* couldn't get DMA channel       */
+#define U14ERR_BADHAND        U14ERR(-10)    /* handle provided was bad        */
+#define U14ERR_BAD1401NUM     U14ERR(-11)    /* 1401 number provided was bad   */
+
+#define U14ERR_NO_SUCH_FN     U14ERR(-20)    /* no such function               */
+#define U14ERR_NO_SUCH_SUBFN  U14ERR(-21)    /* no such sub function           */
+#define U14ERR_NOOUT          U14ERR(-22)    /* no room in output buffer       */
+#define U14ERR_NOIN           U14ERR(-23)    /* no input in buffer             */
+#define U14ERR_STRLEN         U14ERR(-24)    /* string longer than buffer      */
+#define U14ERR_ERR_STRLEN     U14ERR(-24)    /* string longer than buffer      */
+#define U14ERR_LOCKFAIL       U14ERR(-25)    /* failed to lock memory          */
+#define U14ERR_UNLOCKFAIL     U14ERR(-26)    /* failed to unlock memory        */
+#define U14ERR_ALREADYSET     U14ERR(-27)    /* area already set up            */
+#define U14ERR_NOTSET         U14ERR(-28)    /* area not set up                */
+#define U14ERR_BADAREA        U14ERR(-29)    /* illegal area number            */
+#define U14ERR_FAIL           U14ERR(-30)    /* we failed for some other reason*/
+
+#define U14ERR_NOFILE         U14ERR(-40)    /* command file not found         */
+#define U14ERR_READERR        U14ERR(-41)    /* error reading command file     */
+#define U14ERR_UNKNOWN        U14ERR(-42)    /* unknown command                */
+#define U14ERR_HOSTSPACE      U14ERR(-43)    /* not enough host space to load  */
+#define U14ERR_LOCKERR        U14ERR(-44)    /* could not lock resource/command*/
+#define U14ERR_CLOADERR       U14ERR(-45)    /* CLOAD command failed           */
+
+#define U14ERR_TOXXXERR       U14ERR(-60)    /* tohost/1401 failed             */
+#define U14ERR_NO386ENH       U14ERR(-80)    /* not 386 enhanced mode          */
+#define U14ERR_NO1401DRIV     U14ERR(-81)    /* no device driver               */
+#define U14ERR_DRIVTOOOLD     U14ERR(-82)    /* device driver too old          */
+
+#define U14ERR_TIMEOUT        U14ERR(-90)    /* timeout occurred               */
+
+#define U14ERR_BUFF_SMALL     U14ERR(-100)   /* buffer for getstring too small */
+#define U14ERR_CBALREADY      U14ERR(-101)   /* there is already a callback    */
+#define U14ERR_BADDEREG       U14ERR(-102)   /* bad parameter to deregcallback */
+#define U14ERR_NOMEMORY       U14ERR(-103)   /* no memory for allocation       */
+
+#define U14ERR_DRIVCOMMS      U14ERR(-110)   /* failed talking to driver       */
+#define U14ERR_OUTOFMEMORY    U14ERR(-111)   /* needed memory and couldnt get it*/
+
+/// 1401 type codes.
+#define U14TYPE1401           0           /* standard 1401                  */
+#define U14TYPEPLUS           1           /* 1401 plus                      */
+#define U14TYPEU1401          2           /* u1401                          */
+#define U14TYPEPOWER          3           /* power1401                      */
+#define U14TYPEU14012         4           /* u1401 mk II                    */
+#define U14TYPEPOWER2         5           /* power1401 mk II                */
+#define U14TYPEU14013         6           /* u1401-3                        */
+#define U14TYPEPOWER3         7           /* power1401-3                    */
+#define U14TYPEUNKNOWN        -1          /* dont know                      */
+
+/// Transfer flags to allow driver capabilities to be interrogated
+
+/// Constants for transfer flags
+#define U14TF_USEDMA          1           /* Transfer flag for use DMA      */
+#define U14TF_MULTIA          2           /* Transfer flag for multi areas  */
+#define U14TF_FIFO            4           /* for FIFO interface card        */
+#define U14TF_USB2            8           /* for USB2 interface and 1401    */
+#define U14TF_NOTIFY          16          /* for event notifications        */
+#define U14TF_SHORT           32          /* for PCI can short cycle        */
+#define U14TF_PCI2            64          /* for new PCI card 1401-70       */
+#define U14TF_CIRCTH          128         /* Circular-mode to host          */
+#define U14TF_DIAG            256         /* Diagnostics/debug functions    */
+#define U14TF_CIRC14          512         /* Circular-mode to 1401          */
+
+/// Definitions of element sizes for DMA transfers - to allow byte-swapping
+#define ESZBYTES              0           /* BYTE element size value        */
+#define ESZWORDS              1           /* WORD element size value        */
+#define ESZLONGS              2           /* long element size value        */
+#define ESZUNKNOWN            0           /* unknown element size value     */
+
+/// These define required access types for the debug/diagnostics function
+#define BYTE_SIZE             1           /* 8-bit access                   */
+#define WORD_SIZE             2           /* 16-bit access                  */
+#define LONG_SIZE             3           /* 32-bit access                  */
+
+/// Stuff used by U14_GetTransfer
+#define GET_TX_MAXENTRIES  257          /* (max length / page size + 1) */
+
+#ifdef _IS_WINDOWS_
+#pragma pack(1)
+
+typedef struct                          /* used for U14_GetTransfer results */
+{                                          /* Info on a single mapped block */
+   U14LONG physical;
+   U14LONG size;
+} TXENTRY;
+
+typedef struct TGetTxBlock              /* used for U14_GetTransfer results */
+{                                               /* matches structure in VXD */
+   U14LONG size;
+   U14LONG linear;
+   short   seg;
+   short   reserved;
+   short   avail;                      /* number of available entries */
+   short   used;                       /* number of used entries */
+   TXENTRY entries[GET_TX_MAXENTRIES];       /* Array of mapped block info */
+} TGET_TX_BLOCK;
+
+typedef TGET_TX_BLOCK *LPGET_TX_BLOCK;
+
+#pragma pack()
+#endif
+
+#ifdef LINUX
+typedef struct                          /* used for U14_GetTransfer results */
+{                                       /* Info on a single mapped block */
+   long long physical;
+   long     size;
+} TXENTRY;
+
+typedef struct TGetTxBlock              /* used for U14_GetTransfer results */
+{                                       /* matches structure in VXD */
+   long long linear;                    /* linear address */
+   long     size;                       /* total size of the mapped area, holds id when called */
+   short    seg;                        /* segment of the address for Win16 */
+   short    reserved;
+   short    avail;                      /* number of available entries */
+   short    used;                       /* number of used entries */
+   TXENTRY  entries[GET_TX_MAXENTRIES]; /* Array of mapped block info */
+} TGET_TX_BLOCK;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+U14API(int)   U14WhenToTimeOut(short hand);         // when to timeout in ms
+U14API(short) U14PassedTime(int iTime);             // non-zero if iTime passed
+
+U14API(short) U14LastErrCode(short hand);
+
+U14API(short) U14Open1401(short n1401);
+U14API(short) U14Close1401(short hand);
+U14API(short) U14Reset1401(short hand);
+U14API(short) U14ForceReset(short hand);
+U14API(short) U14TypeOf1401(short hand);
+U14API(short) U14NameOf1401(short hand, char* pBuf, WORD wMax);
+
+U14API(short) U14Stat1401(short hand);
+U14API(short) U14CharCount(short hand);
+U14API(short) U14LineCount(short hand);
+
+U14API(short) U14SendString(short hand, const char* pString);
+U14API(short) U14GetString(short hand, char* pBuffer, WORD wMaxLen);
+U14API(short) U14SendChar(short hand, char cChar);
+U14API(short) U14GetChar(short hand, char* pcChar);
+
+U14API(short) U14LdCmd(short hand, const char* command);
+U14API(DWORD) U14Ld(short hand, const char* vl, const char* str);
+
+U14API(short) U14SetTransArea(short hand, WORD wArea, void *pvBuff,
+                                            DWORD dwLength, short eSz);
+U14API(short) U14UnSetTransfer(short hand, WORD wArea);
+U14API(short) U14SetTransferEvent(short hand, WORD wArea, BOOL bEvent,
+                                  BOOL bToHost, DWORD dwStart, DWORD dwLength);
+U14API(int)   U14TestTransferEvent(short hand, WORD wArea);
+U14API(int)   U14WaitTransferEvent(short hand, WORD wArea, int msTimeOut);
+U14API(short) U14GetTransfer(short hand, TGET_TX_BLOCK *pTransBlock);
+
+U14API(short) U14ToHost(short hand, char* pAddrHost,DWORD dwSize,DWORD dw1401,
+                                                            short eSz);
+U14API(short) U14To1401(short hand, const char* pAddrHost,DWORD dwSize,DWORD dw1401,
+                                                            short eSz);
+
+U14API(short) U14SetCircular(short hand, WORD wArea, BOOL bToHost, void *pvBuff,
+                                         DWORD dwLength);
+
+U14API(int)   U14GetCircBlk(short hand, WORD wArea, DWORD *pdwOffs);
+U14API(int)   U14FreeCircBlk(short hand, WORD wArea, DWORD dwOffs, DWORD dwSize,
+                                         DWORD *pdwOffs);
+
+U14API(short) U14StrToLongs(const char* pszBuff, U14LONG *palNums, short sMaxLongs);
+U14API(short) U14LongsFrom1401(short hand, U14LONG *palBuff, short sMaxLongs);
+
+U14API(void)  U14SetTimeout(short hand, int lTimeout);
+U14API(int)   U14GetTimeout(short hand);
+U14API(short) U14OutBufSpace(short hand);
+U14API(int)   U14BaseAddr1401(short hand);
+U14API(int)   U14DriverVersion(short hand);
+U14API(int)   U14DriverType(short hand);
+U14API(short) U14DriverName(short hand, char* pBuf, WORD wMax);
+U14API(short) U14GetUserMemorySize(short hand, DWORD *pMemorySize);
+U14API(short) U14KillIO1401(short hand);
+
+U14API(short) U14BlkTransState(short hand);
+U14API(short) U14StateOf1401(short hand);
+
+U14API(short) U14Grab1401(short hand);
+U14API(short) U14Free1401(short hand);
+U14API(short) U14Peek1401(short hand, DWORD dwAddr, int nSize, int nRepeats);
+U14API(short) U14Poke1401(short hand, DWORD dwAddr, DWORD dwValue, int nSize, int nRepeats);
+U14API(short) U14Ramp1401(short hand, DWORD dwAddr, DWORD dwDef, DWORD dwEnable, int nSize, int nRepeats);
+U14API(short) U14RampAddr(short hand, DWORD dwDef, DWORD dwEnable, int nSize, int nRepeats);
+U14API(short) U14StopDebugLoop(short hand);
+U14API(short) U14GetDebugData(short hand, U14LONG *plValue);
+
+U14API(short) U14StartSelfTest(short hand);
+U14API(short) U14CheckSelfTest(short hand, U14LONG *pData);
+U14API(short) U14TransferFlags(short hand);
+U14API(void)  U14GetErrorString(short nErr, char* pStr, WORD wMax);
+U14API(int)   U14MonitorRev(short hand);
+U14API(void)  U14CloseAll(void);
+
+U14API(short) U14WorkingSet(DWORD dwMinKb, DWORD dwMaxKb);
+U14API(int)   U14InitLib(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* End of ifndef __USE1401_H__ */
diff --git a/drivers/staging/ced1401/use14_ioc.h b/drivers/staging/ced1401/use14_ioc.h
new file mode 100644 (file)
index 0000000..15ca638
--- /dev/null
@@ -0,0 +1,301 @@
+/* use14_ioc.h
+** definitions of use1401 module stuff that is shared between use1401 and the driver.
+** Copyright (C) Cambridge Electronic Design Limited 2010
+** Author Greg P Smith
+************************************************************************************/
+#ifndef __USE14_IOC_H__
+#define __USE14_IOC_H__
+
+#define  MAX_TRANSAREAS   8   /* The number of transfer areas supported by driver */
+
+#define i386
+#include "winioctl.h"                   /* needed so we can access driver   */
+
+/*
+** Defines for IOCTL functions to ask driver to perform. These must be matched
+** in both use1401 and in the driver. The IOCTL code contains a command
+** identifier, plus other information about the device, the type of access
+** with which the file must have been opened, and the type of buffering.
+** The IOCTL function codes from 0x80 to 0xFF are for developer use.
+*/
+#define  FILE_DEVICE_CED1401    0x8001
+#define  FNNUMBASE              0x800
+
+#define  U14_OPEN1401            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE,               \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_CLOSE1401           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+1,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SENDSTRING          CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+2,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_RESET1401           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+3,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETCHAR             CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+4,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SENDCHAR            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+5,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STAT1401            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+6,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_LINECOUNT           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+7,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETSTRING           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+8,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_REGCALLBACK         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+9,             \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETMONITORBUF       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+10,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SETTRANSFER         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+11,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_UNSETTRANSFER       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+12,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SETTRANSEVENT       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+13,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETOUTBUFSPACE      CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+14,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETBASEADDRESS      CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+15,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETDRIVERREVISION   CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+16,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETTRANSFER         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+17,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_KILLIO1401          CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+18,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_BLKTRANSSTATE       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+19,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_BYTECOUNT           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+20,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_ZEROBLOCKCOUNT      CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+21,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STOPCIRCULAR        CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+22,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STATEOF1401         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+23,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_REGISTERS1401       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+24,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GRAB1401            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+25,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_FREE1401            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+26,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STEP1401            CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+27,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SET1401REGISTERS    CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+28,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STEPTILL1401        CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+29,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SETORIN             CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+30,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_STARTSELFTEST       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+31,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_CHECKSELFTEST       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+32,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_TYPEOF1401          CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+33,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_TRANSFERFLAGS       CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+34,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGPEEK             CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+35,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGPOKE             CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+36,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGRAMPDATA         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+37,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGRAMPADDR         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+38,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGGETDATA          CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+39,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_DBGSTOPLOOP         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+40,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_FULLRESET           CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+41,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_SETCIRCULAR         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+42,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_GETCIRCBLK          CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+43,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+#define  U14_FREECIRCBLK         CTL_CODE( FILE_DEVICE_CED1401,     \
+                                           FNNUMBASE+44,            \
+                                           METHOD_BUFFERED,         \
+                                           FILE_ANY_ACCESS)
+
+//--------------- Structures that are shared with the driver -------------
+#pragma pack(1)
+
+typedef struct                  /* used for get/set standard 1401 registers */
+{
+   short   sPC;
+   char    A;
+   char    X;
+   char    Y;
+   char    stat;
+   char    rubbish;
+} T1401REGISTERS;
+
+typedef union     /* to communicate with 1401 driver status & control funcs */
+{
+   char           chrs[22];
+   short          ints[11];
+   long           longs[5];
+   T1401REGISTERS registers;
+} TCSBLOCK;
+
+typedef TCSBLOCK*  LPTCSBLOCK;
+
+typedef struct paramBlk
+{
+    short       sState;
+    TCSBLOCK    csBlock;
+} PARAMBLK;
+
+typedef PARAMBLK*   PPARAMBLK;
+
+typedef struct TransferDesc          /* Structure and type for SetTransArea */
+{
+   WORD        wArea;            /* number of transfer area to set up       */
+   void FAR *  lpvBuff;          /* address of transfer area                */
+   DWORD       dwLength;         /* length of area to set up                */
+   short       eSize;            /* size to move (for swapping on MAC)      */
+} TRANSFERDESC;
+
+typedef TRANSFERDESC FAR *    LPTRANSFERDESC;
+
+/* This is the structure used to set up a transfer area */
+typedef struct VXTransferDesc    /* use1401.c and use1432x.x use only       */
+{
+   WORD        wArea;            /* number of transfer area to set up       */
+   WORD        wAddrSel;         /* 16 bit selector for area                */
+   DWORD       dwAddrOfs;        /* 32 bit offset for area start            */
+   DWORD       dwLength;         /* length of area to set up                */
+} VXTRANSFERDESC;
+
+#pragma pack()
+
+#endif
\ No newline at end of file
diff --git a/drivers/staging/ced1401/userspace/use1401.c b/drivers/staging/ced1401/userspace/use1401.c
new file mode 100644 (file)
index 0000000..d4c6316
--- /dev/null
@@ -0,0 +1,3035 @@
+/****************************************************************************
+** use1401.c
+** Copyright (C) Cambridge Electronic Design Ltd, 1992-2010
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public License
+** as published by the Free Software Foundation; either version 2
+** of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program; if not, write to the Free Software
+** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+**
+** Contact CED: Cambridge Electronic Design Limited, Science Park, Milton Road
+**              Cambridge, CB6 0FE.
+**              www.ced.co.uk
+**              greg@ced.co.uk
+**
+**  Title:      USE1401.C
+**  Version:    4.00
+**  Author:     Paul Cox, Tim Bergel, Greg Smith
+**
+** The code was vigorously pruned in DEC 2010 to remove the macintosh options
+** and to get rid of the 16-bit support. It has also been aligned with the
+** Linux version. See CVS for revisions. This will work for Win 9x onwards.
+****************************************************************************
+**
+** Notes on Windows interface to driver
+** ************************************
+**
+** Under Windows 9x and NT, Use1401 uses DeviceIoControl to get access to
+** the 1401 driver. This has parameters for the device handle, the function
+** code, an input pointer and byte count, an output pointer and byte count
+** and a pointer to a DWORD to hold the output byte count. Note that input
+** and output are from the point-of-view of the driver, so the output stuff
+** is used to read values from the 1401, not send to the 1401. The use of
+** these parameters varies with the function in use and the operating
+** system; there are five separate DIOC calls SendString, GetString and
+** SetTransferArea all have their own specialised calls, the rest use the
+** Status1401 or Control1401 functions.
+**
+** There are two basic styles of DIOC call used, one for Win9x VxD drivers
+** and one for NT Kernel-mode and WDM drivers (see below for tables showing
+** the different parameters used. The array bUseNTDIOC[] selects between
+** these two calling styles.
+**
+** Function codes
+** In Win3.x, simple function codes from 0 to 40 were used, shifted left 8
+** bits with a sub-function code in the lower 8 bits. These were also used
+** in the Windows 95 driver, though we had to add 1 to the code value to
+** avoid problems (Open from CreateFile is zero), and the sub-function code
+** is now unused. We found that this gave some problems with Windows 98
+** as the function code values are reserved by microsoft, so we switched to
+** using the NT function codes instead. The NT codes are generated using the
+** CTL_CODE macro, essentially this gives 0x80012000 | (func << 2), where
+** func is the original 0 to 34 value. The driver will handle both types of
+** code and Use1432 only uses the NT codes if it knows the driver is new
+** enough. The array bUseNTCodes[] holds flags on the type of codes required.
+** GPS/TDB Dec 2010: we removed the bUseNTCodes array as this is always true
+** as we no longer support ancient versions.
+**
+** The CreateFile and CloseFile function calls are also handled
+** by DIOC, using the special function codes 0 and -1 respectively.
+**
+** Input pointer and buffer size
+** These are intended for data sent to the device driver. In nearly all cases
+** they are unused in calls to the Win95 driver, the NT driver uses them
+** for all information sent to the driver. The table below shows the pointer
+** and byte count used for the various calls:
+**
+**                      Win 95                  Win NT
+** SendString           NULL, 0                 pStr, nStr
+** GetString            NULL, 0                 NULL, 0
+** SetTransferArea      pBuf, nBuf (unused?)    pDesc, nDesc
+** GetTransfer          NULL, 0                 NULL, 0
+** Status1401           NULL, 0                 NULL, 0
+** Control1401          NULL, 0                 pBlk, nBlk
+**
+** pStr and nStr are pointers to a char buffer and the buffer length for
+** string I/O, note that these are temporary buffers owned by the DLL, not
+** application memory, pBuf and nBuf are the transfer area buffer (I think
+** these are unused), pDesc and nDesc are the TRANSFERDESC structure, pBlk
+** and nBlk are the TCSBLOCK structure.
+**
+**
+** Output pointer and buffer size
+** These are intended for data read from the device driver. These are used
+** for almost all information sent to the Win95 driver, the NT driver uses
+** them for information read from the driver, chiefly the error code. The
+** table below shows the pointer and byte count used for the various calls:
+**
+**                      Win 95                  Win NT
+** SendString           pStr, nStr              pPar, nPar
+** GetString            pStr, nStr+2            pStr, nStr+2
+** SetTransferArea      pDesc, nDesc            pPar, nPar
+** GetTransfer          pGet, nGet              pGet, nGet
+** Status1401           pBlk, nBlk              pPar, nPar
+** Control1401          pBlk, nBlk              pPar, nPar
+**
+** pStr and nStr are pointers to a char buffer and the buffer length for
+** string I/O, the +2 for GetString refers to two spare bytes at the start
+** used to hold the string length and returning an error code for NT. Note
+** again that these are (and must be) DLL-owned temporary buffers. pPar
+** and nPar are a PARAM structure used in NT (it holds an error code and a 
+** TCSBLOCK structure). pDesc and nDesc are the VXTRANSFERDESC structure,
+** pBlk and nBlk are the TCSBLOCK structure. pGet and nGet indicate the
+** TGET_TX_BLOCK structure used for GetTransfer.
+**
+**
+** The output byte count
+** Both drivers return the output buffer size here, regardless of the actual
+** bytes output. This is used to check that we did get through to the driver.
+**
+** Multiple 1401s
+** **************
+**
+** We have code that tries to support the use of multiple 1401s, but there
+** are problems: The lDriverVersion and lDriverType variables are global, not
+** per-1401 (a particular problem as the U14 functions that use them don't
+** have a hand parameter). In addition, the mechansim for finding a free
+** 1401 depends upon the 1401 device driver open operation failing if it's
+** already in use, which doesn't always happen, particularly with the VxDs.
+** The code in TryToOpen tries to fix this by relying on TYPEOF1401 to detect
+** the 1401-in-use state - the VxDs contain special code to help this. This is
+** working OK but multiple 1401 support works better with the Win2000 drivers.
+**
+** USB driver
+** **********
+**
+** The USB driver, which runs on both Win98 and NT2000, uses the NT-style
+** calling convention, both for the DIOC codes and the DIOC parameters. The
+** TryToOpen function has been altered to look for an NT driver first in
+** the appropriate circumstances, and to set the driver DIOC flags up in
+** the correct state.
+**
+** Adding a new 1401 type - now almost nothing to do
+** *************************************************
+**
+** The 1401 types are defined by a set of U14TYPExxxx codes in USE1401.H.
+** You should add a new one of these to keep things tidy for applications.
+**
+** DRIVERET_MAX (below) specifies the maximum allowed type code from the
+** 1401 driver; I have set this high to accomodate as yet undesigned 1401
+** types. Similarly, as long as the command file names follow the ARM,
+** ARN, ARO sequence, these are calculated by the ExtForType function, so
+** you don't need to do anything here either.
+**
+** Version number
+** **************
+** The new U14InitLib() function returns 0 if the OS is incapable of use,
+** otherwise is returns the version of the USE1401 library. This is done
+** in three parts: Major(31-24).Minor(23-16).Revision.(15-0) (brackets are
+** the bits used). The Major number starts at 2 for the first revision with
+** the U14InitLib() function. Changes to the Major version means that we
+** have broken backwards compatibility. Minor number changes mean that we
+** have added new functionality that does not break backwards compatibility.
+** we starts at 0. Revision changes mean we have fixed something. Each index
+** returns to 0 when a higer one changes.
+*/
+#define U14LIB_MAJOR 4
+#define U14LIB_MINOR 0
+#define U14LIB_REVISION 0
+#define U14LIB_VERSION ((U14LIB_MAJOR<<24) | (U14LIB_MINOR<<16) | U14LIB_REVISION)
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "USE1401.H"
+
+#ifdef _IS_WINDOWS_
+#include <io.h>
+#include <windows.h>
+#pragma warning(disable: 4100) /* Disable "Unused formal parameter" warning */
+#include <assert.h>
+#include "process.h"
+
+
+#define sprintf wsprintf
+#define PATHSEP '\\'
+#define PATHSEPSTR "\\"
+#define DEFCMDPATH "\\1401\\"   // default command path if all else fails
+#define MINDRIVERMAJREV 1       // minimum driver revision level we need
+#define __packed                // does nothing in Windows
+
+#include "use14_ioc.h"          // links to device driver stuff
+#endif
+
+#ifdef LINUX
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sched.h>
+#include <libgen.h>
+#define PATHSEP '/'
+#define PATHSEPSTR "/"
+#define DEFCMDPATH "/var/1401/" // default command path if all else fails
+#define MINDRIVERMAJREV 2       // minimum driver revision level we need
+
+#include "ced_ioctl.h"          // links to device driver stuff
+#endif
+
+#define MAX1401         8       // The number of 1401s that can be supported
+
+/*
+** These are the 1401 type codes returned by the driver, they are a slightly
+** odd sequence & start for reasons of compatability with the DOS driver.
+** The maximum code value is the upper limit of 1401 device types.
+*/
+#define DRIVRET_STD     4       // Codes for 1401 types matching driver values
+#define DRIVRET_U1401   5       // This table does not need extending, as
+#define DRIVRET_PLUS    6       // we can calculate values now.
+#define DRIVRET_POWER   7       // but we need all of these values still
+#define DRIVRET_MAX     26      // Maximum tolerated code - future designs
+
+/*
+** These variables store data that will be used to generate the last
+** error string. For now, a string will hold the 1401 command file name.
+*/
+static char szLastName[20];     // additional text information
+
+/*
+** Information stored per handle. NBNB, driverType and DriverVersion used to be
+** only stored once for all handles... i.e. nonsensical. This change means that
+** three U14...() calls now include handles that were previously void. We have
+** set a constructor and a destructor call for the library (see the end) to
+** initialise important structures, or call use1401_load().
+*/
+static short asDriverType[MAX1401] = {0};
+static int lLastDriverVersion = U14ERR_NO1401DRIV;
+static int lLastDriverType = U14TYPEUNKNOWN;
+static int alDriverVersion[MAX1401];            // version/type of each driver
+static int alTimeOutPeriod[MAX1401];            // timeout time in milliseconds
+static short asLastRetCode[MAX1401];            // last code from a fn call
+static short asType1401[MAX1401] = {0};         // The type of the 1401
+static BOOL abGrabbed[MAX1401] = {0};           // Flag for grabbed, set true by grab1401
+static int iAttached = 0;                       // counts process attaches so can let go
+
+#ifdef _IS_WINDOWS_
+/****************************************************************************
+** Windows NT Specific Variables and internal types
+****************************************************************************/
+static HANDLE aHand1401[MAX1401] = {0};         // handles for 1401s
+static HANDLE aXferEvent[MAX1401] = {0};        // transfer events for the 1401s
+static LPVOID apAreas[MAX1401][MAX_TRANSAREAS]; // Locked areas
+static DWORD  auAreas[MAX1401][MAX_TRANSAREAS]; // Size of locked areas
+static BOOL   bWindows9x = FALSE;               // if we are Windows 95 or better
+#ifdef _WIN64
+#define USE_NT_DIOC(ind) TRUE
+#else
+static BOOL   abUseNTDIOC[MAX1401];             // Use NT-style DIOC parameters */
+#define USE_NT_DIOC(ind) abUseNTDIOC[ind]
+#endif
+
+#endif
+
+#ifdef LINUX
+static int aHand1401[MAX1401] = {0};    // handles for 1401s
+#define INVALID_HANDLE_VALUE 0          // to avoid code differences
+#endif
+
+
+/*
+** The CmdHead relates to backwards compatibility with ancient Microsoft (and Sperry!)
+** versions of BASIC, where this header was needed so we could load a command into
+** memory.
+*/
+#pragma pack(1)                 // pack our structure
+typedef struct CmdHead          // defines header block on command
+{                               // for PC commands
+   char   acBasic[5];           // BASIC information - needed to align things
+   WORD   wBasicSz;             // size as seen by BASIC
+   WORD   wCmdSize;             // size of the following info
+} __packed CMDHEAD;
+#pragma pack()                  // back to normal
+
+/*
+** The rest of the header looks like this...
+**  int    iRelPnt;             relocation pointer... actual start
+**  char   acName[8];           string holding the command name
+**  BYTE   bMonRev;             monitor revision level
+**  BYTE   bCmdRev;             command revision level
+*/
+
+typedef CMDHEAD *LPCMDHEAD;     // pointer to a command header
+
+#define  MAXSTRLEN   255        // maximum string length we use
+#define  TOHOST      FALSE
+#define  TO1401      TRUE
+
+static short CheckHandle(short h)
+{
+    if ((h < 0) || (h >= MAX1401))  // must be legal range...
+        return U14ERR_BADHAND;
+    if (aHand1401[h] <= 0)          // must be open
+        return U14ERR_BADHAND;
+    return U14ERR_NOERROR;
+}
+
+#ifdef _IS_WINDOWS_
+/****************************************************************************
+** U14Status1401    Used for functions which do not pass any data in but
+**                  get data back
+****************************************************************************/
+static short U14Status1401(short sHand, LONG lCode, TCSBLOCK* pBlk)
+{
+    DWORD dwBytes = 0;
+
+    if ((sHand < 0) || (sHand >= MAX1401))  /* Check parameters */
+        return U14ERR_BADHAND;
+#ifndef _WIN64
+    if (!USE_NT_DIOC(sHand)) 
+    {   /* Windows 9x DIOC methods? */
+        if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, pBlk,sizeof(TCSBLOCK),&dwBytes,NULL))
+            return (short)((dwBytes>=sizeof(TCSBLOCK)) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS);
+        else
+            return (short)GetLastError();
+    }
+    else
+#endif
+    {                                       /* Windows NT or USB driver */
+        PARAMBLK rWork;
+        rWork.sState = U14ERR_DRIVCOMMS;
+        if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, &rWork,sizeof(PARAMBLK),&dwBytes,NULL) &&
+            (dwBytes >= sizeof(PARAMBLK)))
+        {
+            *pBlk = rWork.csBlock;
+            return rWork.sState;
+        }
+    }
+
+    return U14ERR_DRIVCOMMS;
+}
+
+/****************************************************************************
+** U14Control1401   Used for functions which pass data in and only expect
+**                  an error code back
+****************************************************************************/
+static short U14Control1401(short sHand, LONG lCode, TCSBLOCK* pBlk)
+{
+    DWORD dwBytes = 0;
+
+    if ((sHand < 0) || (sHand >= MAX1401))              /* Check parameters */
+        return U14ERR_BADHAND;
+
+#ifndef _WIN64
+    if (!USE_NT_DIOC(sHand))                    
+    {                            /* Windows 9x DIOC methods */
+        if (DeviceIoControl(aHand1401[sHand], lCode, NULL, 0, pBlk, sizeof(TCSBLOCK), &dwBytes, NULL))
+            return (short)(dwBytes >= sizeof(TCSBLOCK) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS);
+        else
+            return (short)GetLastError();
+    }
+    else
+#endif
+    {                            /* Windows NT or later */
+        PARAMBLK rWork;
+        rWork.sState = U14ERR_DRIVCOMMS;
+        if (DeviceIoControl(aHand1401[sHand], lCode, pBlk, sizeof(TCSBLOCK), &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+            (dwBytes >= sizeof(PARAMBLK)))
+            return rWork.sState;
+    }
+
+    return U14ERR_DRIVCOMMS;
+}
+#endif
+
+/****************************************************************************
+** SafeTickCount
+** Gets time in approximately units of a millisecond.
+*****************************************************************************/
+static long SafeTickCount()
+{
+#ifdef _IS_WINDOWS_
+    return GetTickCount();
+#endif
+#ifdef LINUX
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    return (tv.tv_sec*1000 + tv.tv_usec/1000);
+#endif
+}
+
+/****************************************************************************
+** A utility routine to get the command file extension for a given type
+** of 1401. We assume the type code is vaguely legal.
+****************************************************************************/
+static int ExtForType(short sType, char* szExt)
+{
+    szExt[0] = 0;                       /* Default return is a blank string */
+    switch (sType)
+    {
+    case U14TYPE1401: strcpy(szExt, ".CMD");  break;    // Standard 1401
+    case U14TYPEPLUS: strcpy(szExt, ".GXC");  break;    // 1401 plus
+    default:               // All others are in a predictable sequence
+        strcpy(szExt, ".ARM");
+            szExt[3] = (char)('M' + sType - U14TYPEU1401);
+        if (szExt[3] > 'Z')             // Wrap round to ARA after ARZ
+                szExt[3] = (char)(szExt[3] - 26);
+    }
+    return 0;
+}
+
+/****************************************************************************
+**   U14WhenToTimeOut
+**       Returns the time to time out in time units suitable for the machine
+** we are running on  ie millsecs for pc/linux, or Mac/
+****************************************************************************/
+U14API(int) U14WhenToTimeOut(short hand)
+{
+    int iNow = SafeTickCount();
+    if ((hand >= 0) && (hand < MAX1401))
+        iNow += alTimeOutPeriod[hand];
+    return iNow;
+}
+
+/****************************************************************************
+** U14PassedTime
+** Returns non zero if the timed passed in has been passed 0 if not
+****************************************************************************/
+U14API(short) U14PassedTime(int lCheckTime)
+{
+    return (short)((SafeTickCount()-lCheckTime) > 0);
+}
+
+/****************************************************************************
+** TranslateString
+** Tidies up string that U14GetString returns. Converts all the commas in a
+** string to spaces. Removes terminating CR character. May do more in future.
+****************************************************************************/
+static void TranslateString(char* pStr)
+{
+    int i = 0;
+    while (pStr[i])
+    {
+        if (pStr[i] == ',')
+            pStr[i] = ' ';              /* convert comma to space */
+        ++i;
+    }
+
+    if ((i > 0) && (pStr[i-1] == '\n'))  /* kill terminating LF */
+        pStr[i-1] = (char)0;
+}
+
+/****************************************************************************
+** U14StrToLongs
+** Converts a string to an array of longs and returns the number of values
+****************************************************************************/
+U14API(short) U14StrToLongs(const char* pszBuff, U14LONG *palNums, short sMaxLongs)
+{
+    WORD wChInd = 0;                // index into source
+    short sLgInd = 0;               // index into result longs
+
+    while (pszBuff[wChInd] &&       // until we get to end of string...
+           (sLgInd < sMaxLongs))    // ...or filled the buffer
+    {
+        // Why not use a C Library converter?
+        switch (pszBuff[wChInd])
+        {
+        case '-':
+        case '0': case '1':   case '2': case '3':   case '4':
+        case '5': case '6':   case '7': case '8':   case '9':
+            {
+                BOOL bDone = FALSE; // true at end of number
+                int iSign = 1;      // sign of number
+                long lValue = 0;
+
+                while ((!bDone) && pszBuff[wChInd])
+                {
+                    switch (pszBuff[wChInd])
+                    {
+                    case '-':
+                        iSign = -1; // swap sign
+                        break;
+
+                    case '0': case '1':   case '2': case '3':   case '4':
+                    case '5': case '6':   case '7': case '8':   case '9':
+                        lValue *= 10;   // move to next digit base 10
+                        lValue += ((int)pszBuff[wChInd]-(int)'0');
+                        break;
+
+                    default:        // end of number
+                        bDone = TRUE;
+                        break;
+                    }
+                    wChInd++;       // move onto next character
+                }
+                palNums[sLgInd] = lValue * iSign;
+                sLgInd++;
+            }
+            break;
+
+        default:
+            wChInd++;               // look at next char
+            break;
+        }
+    }
+    return (sLgInd);
+}
+
+
+/****************************************************************************
+** U14LongsFrom1401
+** Gets the next waiting line from the 1401 and converts it longs
+** Returns the number of numbers read or an error.
+****************************************************************************/
+U14API(short) U14LongsFrom1401(short hand, U14LONG *palBuff, short sMaxLongs)
+{
+    char szWork[MAXSTRLEN];
+    short sResult = U14GetString(hand, szWork, MAXSTRLEN);/* get reply from 1401   */
+    if (sResult == U14ERR_NOERROR)                  /* if no error convert   */
+        sResult = U14StrToLongs(szWork, palBuff, sMaxLongs);
+    return sResult;
+}
+
+/****************************************************************************
+**   U14CheckErr
+**   Sends the ERR command to the 1401 and gets the result. Returns 0, a
+**   negative error code, or the first error value.
+****************************************************************************/
+U14API(short) U14CheckErr(short hand)
+{
+    short sResult = U14SendString(hand, ";ERR;");
+    if (sResult == U14ERR_NOERROR)
+    {
+        U14LONG er[3];
+        sResult = U14LongsFrom1401(hand, er, 3);
+        if (sResult > 0)
+        {
+            sResult = (short)er[0];        /* Either zero or an error value */
+#ifdef _DEBUG
+            if (er[0] != 0)
+            {
+                char szMsg[50];
+                sprintf(szMsg, "U14CheckErr returned %d,%d\n", er[0], er[1]);
+                OutputDebugString(szMsg);
+            }
+#endif
+        }
+        else
+        {
+            if (sResult == 0)
+                sResult = U14ERR_TIMEOUT;      /* No numbers equals timeout */
+        }
+    }
+
+    return sResult;
+}
+
+/****************************************************************************
+** U14LastErrCode
+** Returns the last code from the driver. This is for Windows where all calls
+** go through the Control and Status routines, so we can save any error.
+****************************************************************************/
+U14API(short) U14LastErrCode(short hand)
+{
+    if ((hand < 0) || (hand >= MAX1401))
+        return U14ERR_BADHAND;
+    return asLastRetCode[hand];
+}
+
+/****************************************************************************
+** U14SetTimeout
+** Set the timeout period for 1401 comms in milliseconds
+****************************************************************************/
+U14API(void) U14SetTimeout(short hand, int lTimeOut)
+{
+    if ((hand < 0) || (hand >= MAX1401))
+        return;
+    alTimeOutPeriod[hand] = lTimeOut;
+}
+
+/****************************************************************************
+** U14GetTimeout
+** Get the timeout period for 1401 comms in milliseconds
+****************************************************************************/
+U14API(int) U14GetTimeout(short hand)
+{
+    if ((hand < 0) || (hand >= MAX1401))
+        return U14ERR_BADHAND;
+    return alTimeOutPeriod[hand];
+}
+
+/****************************************************************************
+** U14OutBufSpace
+** Return the space in the output buffer, or an error.
+****************************************************************************/
+U14API(short) U14OutBufSpace(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_GETOUTBUFSPACE,&csBlock);
+    if (sErr == U14ERR_NOERROR)
+        sErr = csBlock.ints[0];
+    return sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_GetOutBufSpace(aHand1401[hand]) : sErr;
+#endif
+}
+
+
+/****************************************************************************
+** U14BaseAddr1401
+** Returns the 1401 base address or an error code. Meaningless nowadays
+****************************************************************************/
+U14API(int) U14BaseAddr1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    int iError = U14Status1401(hand, U14_GETBASEADDRESS,&csBlock);
+    if (iError == U14ERR_NOERROR)
+        iError = csBlock.longs[0];
+    return iError;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_GetBaseAddress(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14StateOf1401
+** Return error state, either NOERROR or a negative code.
+****************************************************************************/
+U14API(short) U14StateOf1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_STATEOF1401, &csBlock);
+    if (sErr == U14ERR_NOERROR)
+    {
+        sErr = csBlock.ints[0];      // returned 1401 state
+        if ((sErr >= DRIVRET_STD) && (sErr <= DRIVRET_MAX))
+            sErr = U14ERR_NOERROR;
+    }
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        sErr = (short)CED_StateOf1401(aHand1401[hand]);
+        if ((sErr >= DRIVRET_STD) && (sErr <= DRIVRET_MAX))
+            sErr = U14ERR_NOERROR;
+    }
+#endif
+    return sErr;
+}
+
+/****************************************************************************
+** U14DriverVersion
+** Returns the driver version. Hi word is major revision, low word is minor.
+** If you pass in a silly handle (like -1), we return the version of the last
+** driver we know of (to cope with PCI and no 1401 attached).
+****************************************************************************/
+U14API(int) U14DriverVersion(short hand)
+{
+    return CheckHandle(hand) != U14ERR_NOERROR ? lLastDriverVersion : alDriverVersion[hand];
+}
+
+/****************************************************************************
+** U14DriverType
+** Returns the driver type. The type, 0=ISA/NU-Bus, 1=PCI, 2=USB, 3=HSS
+** If you pass in a silly handle (like -1), we return the type of the last
+** driver we know of (to cope with PCI and no 1401 attached).
+****************************************************************************/
+U14API(int) U14DriverType(short hand)
+{
+    return CheckHandle(hand) != U14ERR_NOERROR ? lLastDriverType : asDriverType[hand];
+}
+
+/****************************************************************************
+** U14DriverName
+** Returns the driver type as 3 character (ISA, PCI, USB or HSS))
+****************************************************************************/
+U14API(short) U14DriverName(short hand, char* pBuf, WORD wMax)
+{
+    char* pName;
+    *pBuf = 0;                             // Start off with a blank string
+    switch (U14DriverType(hand))           // Results according to type
+    {
+    case 0:  pName = "ISA"; break;
+    case 1:  pName = "PCI"; break;
+    case 2:  pName = "USB"; break;
+    case 3:  pName = "HSS"; break;
+    default: pName = "???"; break;
+    }
+    strncpy(pBuf, pName, wMax);            // Copy the correct name to return
+
+    return U14ERR_NOERROR;
+}
+
+/****************************************************************************
+** U14BlkTransState
+** Returns 0 no transfer in progress, 1 transfer in progress or an error code
+****************************************************************************/
+U14API(short) U14BlkTransState(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_BLKTRANSSTATE, &csBlock);
+    if (sErr == U14ERR_NOERROR)
+        sErr = csBlock.ints[0];
+    return sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_BlkTransState(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14Grab1401
+** Take control of the 1401 for diagnostics purposes. USB does nothing.
+****************************************************************************/
+U14API(short) U14Grab1401(short hand)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+#ifdef _IS_WINDOWS_
+        if (abGrabbed[hand])            // 1401 should not have been grabbed
+            sErr = U14ERR_ALREADYSET;   // Error code defined for this
+        else
+        {
+            TCSBLOCK csBlock;
+            sErr = U14Control1401(hand, U14_GRAB1401, &csBlock);
+        }
+#endif
+#ifdef LINUX
+        // 1401 should not have been grabbed
+        sErr = abGrabbed[hand] ? U14ERR_ALREADYSET : CED_Grab1401(aHand1401[hand]);
+#endif
+        if (sErr == U14ERR_NOERROR)
+            abGrabbed[hand] = TRUE;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14Free1401
+****************************************************************************/
+U14API(short)  U14Free1401(short hand)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+#ifdef _IS_WINDOWS_
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+            TCSBLOCK csBlock;
+            sErr = U14Control1401(hand, U14_FREE1401, &csBlock);
+        }
+        else
+            sErr = U14ERR_NOTSET;
+#endif
+#ifdef LINUX
+        // 1401 should not have been grabbed
+        sErr = abGrabbed[hand] ? CED_Free1401(aHand1401[hand]) : U14ERR_NOTSET;
+#endif
+        if (sErr == U14ERR_NOERROR)
+            abGrabbed[hand] = FALSE;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14Peek1401
+** DESCRIPTION  Cause the 1401 to do one or more peek operations.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+** is called. After the peek is done, use U14GetDebugData to retrieve
+** the results of the peek.
+****************************************************************************/
+U14API(short) U14Peek1401(short hand, DWORD dwAddr, int nSize, int nRepeats)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+#ifdef _IS_WINDOWS_
+            TCSBLOCK csBlock;
+            csBlock.longs[0] = (long)dwAddr;
+            csBlock.longs[1] = nSize;
+            csBlock.longs[2] = nRepeats;
+            sErr = U14Control1401(hand, U14_DBGPEEK, &csBlock);
+#endif
+#ifdef LINUX
+            TDBGBLOCK dbb;
+            dbb.iAddr = (int)dwAddr;
+            dbb.iWidth = nSize;
+            dbb.iRepeats = nRepeats;
+            sErr = CED_DbgPeek(aHand1401[hand], &dbb);
+#endif
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14Poke1401
+** DESCRIPTION  Cause the 1401 to do one or more poke operations.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+** is called.
+****************************************************************************/
+U14API(short) U14Poke1401(short hand, DWORD dwAddr, DWORD dwValue,
+                                      int nSize, int nRepeats)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+#ifdef _IS_WINDOWS_
+            TCSBLOCK csBlock;
+            csBlock.longs[0] = (long)dwAddr;
+            csBlock.longs[1] = nSize;
+            csBlock.longs[2] = nRepeats;
+            csBlock.longs[3] = (long)dwValue;
+            sErr = U14Control1401(hand, U14_DBGPOKE, &csBlock);
+#endif
+#ifdef LINUX
+            TDBGBLOCK dbb;
+            dbb.iAddr = (int)dwAddr;
+            dbb.iWidth = nSize;
+            dbb.iRepeats= nRepeats;
+            dbb.iData = (int)dwValue;
+            sErr = CED_DbgPoke(aHand1401[hand], &dbb);
+#endif
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14Ramp1401
+** DESCRIPTION  Cause the 1401 to loop, writing a ramp to a location.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop.
+****************************************************************************/
+U14API(short) U14Ramp1401(short hand, DWORD dwAddr, DWORD dwDef, DWORD dwEnable,
+                                      int nSize, int nRepeats)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+#ifdef _IS_WINDOWS_
+            TCSBLOCK csBlock;
+            csBlock.longs[0] = (long)dwAddr;
+            csBlock.longs[1] = (long)dwDef;
+            csBlock.longs[2] = (long)dwEnable;
+            csBlock.longs[3] = nSize;
+            csBlock.longs[4] = nRepeats;
+            sErr = U14Control1401(hand, U14_DBGRAMPDATA, &csBlock);
+#endif
+#ifdef LINUX
+            TDBGBLOCK dbb;
+            dbb.iAddr = (int)dwAddr;
+            dbb.iDefault = (int)dwDef;
+            dbb.iMask = (int)dwEnable;
+            dbb.iWidth = nSize;
+            dbb.iRepeats = nRepeats;
+            sErr = CED_DbgRampAddr(aHand1401[hand], &dbb);
+#endif
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14RampAddr
+** DESCRIPTION  Cause the 1401 to loop, reading from a ramping location.
+** If lRepeats is zero, the loop will continue until U14StopDebugLoop
+****************************************************************************/
+U14API(short) U14RampAddr(short hand, DWORD dwDef, DWORD dwEnable,
+                                      int nSize, int nRepeats)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+#ifdef _IS_WINDOWS_
+            TCSBLOCK csBlock;
+            csBlock.longs[0] = (long)dwDef;
+            csBlock.longs[1] = (long)dwEnable;
+            csBlock.longs[2] = nSize;
+            csBlock.longs[3] = nRepeats;
+            sErr = U14Control1401(hand, U14_DBGRAMPADDR, &csBlock);
+#endif
+#ifdef LINUX
+            TDBGBLOCK dbb;
+            dbb.iDefault = (int)dwDef;
+            dbb.iMask = (int)dwEnable;
+            dbb.iWidth = nSize;
+            dbb.iRepeats = nRepeats;
+            sErr = CED_DbgRampAddr(aHand1401[hand], &dbb);
+#endif
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+**    U14StopDebugLoop
+**    DESCRIPTION Stops a peek\poke\ramp that, with repeats set to zero,
+**    will otherwise continue forever.
+****************************************************************************/
+U14API(short) U14StopDebugLoop(short hand)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+#ifdef _IS_WINDOWS_
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+            TCSBLOCK csBlock;
+            sErr = U14Control1401(hand, U14_DBGSTOPLOOP, &csBlock);
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+#endif
+#ifdef LINUX
+        sErr = abGrabbed[hand] ? CED_DbgStopLoop(aHand1401[hand]) : U14ERR_NOTSET;
+#endif
+    return sErr;
+}
+
+/****************************************************************************
+** U14GetDebugData
+** DESCRIPTION Returns the result from a previous peek operation.
+****************************************************************************/
+U14API(short) U14GetDebugData(short hand, U14LONG* plValue)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        if (abGrabbed[hand])    // 1401 should have been grabbed
+        {
+#ifdef _IS_WINDOWS_
+            TCSBLOCK csBlock;
+            sErr = U14Status1401(hand, U14_DBGGETDATA, &csBlock);
+            if (sErr == U14ERR_NOERROR)
+                *plValue = csBlock.longs[0];    // Return the data
+#endif
+#ifdef LINUX
+            TDBGBLOCK dbb;
+            sErr = CED_DbgGetData(aHand1401[hand], &dbb);
+            if (sErr == U14ERR_NOERROR)
+                *plValue = dbb.iData;                     /* Return the data */
+#endif
+        }
+        else
+            sErr = U14ERR_NOTSET;
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14StartSelfTest
+****************************************************************************/
+U14API(short) U14StartSelfTest(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    return U14Control1401(hand, U14_STARTSELFTEST, &csBlock);
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_StartSelfTest(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14CheckSelfTest
+****************************************************************************/
+U14API(short) U14CheckSelfTest(short hand, U14LONG *pData)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_CHECKSELFTEST, &csBlock);
+    if (sErr == U14ERR_NOERROR)
+    {
+        pData[0] = csBlock.longs[0];        /* Return the results to user */
+        pData[1] = csBlock.longs[1];
+        pData[2] = csBlock.longs[2];
+    }
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)                /* Check parameters */
+    {
+        TGET_SELFTEST gst;
+        sErr = CED_CheckSelfTest(aHand1401[hand], &gst);
+        if (sErr == U14ERR_NOERROR)
+        {
+            pData[0] = gst.code;        /* Return the results to user */
+            pData[1] = gst.x;
+            pData[2] = gst.y;
+        }
+    }
+#endif
+    return sErr;
+}
+
+/****************************************************************************
+** U14GetUserMemorySize
+****************************************************************************/
+U14API(short) U14GetUserMemorySize(short hand, DWORD *pMemorySize)
+{
+    // The original 1401 used a different command for getting the size
+    short sErr = U14SendString(hand, (asType1401[hand] == U14TYPE1401) ? "MEMTOP;" : "MEMTOP,?;");
+    *pMemorySize = 0;         /* if we get error then leave size set at 0  */
+    if (sErr == U14ERR_NOERROR)
+    {
+        U14LONG alLimits[4];
+        sErr = U14LongsFrom1401(hand, alLimits, 4);
+        if (sErr > 0)              /* +ve sErr is the number of values read */
+        {
+            sErr = U14ERR_NOERROR;                  /* All OK, flag success */
+            if (asType1401[hand] == U14TYPE1401)    /* result for standard  */
+                *pMemorySize = alLimits[0] - alLimits[1]; /* memtop-membot */
+            else
+                *pMemorySize = alLimits[0];   /* result for plus or u1401  */
+        }
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14TypeOf1401
+** Returns the type of the 1401, maybe unknown
+****************************************************************************/
+U14API(short) U14TypeOf1401(short hand)
+{
+    if ((hand < 0) || (hand >= MAX1401))                /* Check parameters */
+        return U14ERR_BADHAND;
+    else
+        return asType1401[hand];
+}
+
+/****************************************************************************
+** U14NameOf1401
+** Returns the type of the 1401 as a string, blank if unknown
+****************************************************************************/
+U14API(short) U14NameOf1401(short hand, char* pBuf, WORD wMax)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+    char* pName;
+    switch (asType1401[hand])               // Results according to type
+    {
+    case U14TYPE1401:  pName = "Std 1401"; break;
+    case U14TYPEPLUS:  pName = "1401plus"; break;
+    case U14TYPEU1401: pName = "micro1401"; break;
+    case U14TYPEPOWER: pName = "Power1401"; break;
+    case U14TYPEU14012:pName = "Micro1401 mk II"; break;
+    case U14TYPEPOWER2:pName = "Power1401 mk II"; break;
+    case U14TYPEU14013:pName = "Micro1401-3"; break;
+    case U14TYPEPOWER3:pName = "Power1401-3"; break;
+    default:           pName = "Unknown";
+    }
+        strncpy(pBuf, pName, wMax);
+    }
+    return sErr;
+}
+
+/****************************************************************************
+** U14TransferFlags
+**  Returns the driver block transfer flags.
+**  Bits can be set - see U14TF_ constants in use1401.h
+*****************************************************************************/
+U14API(short) U14TransferFlags(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_TRANSFERFLAGS, &csBlock);
+    return (sErr == U14ERR_NOERROR) ? (short)csBlock.ints[0] : sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_TransferFlags(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** GetDriverVersion
+** Actually reads driver version from the device driver.
+** Hi word is major revision, low word is minor revision.
+** Assumes that hand has been checked. Also codes driver type in bits 24 up.
+*****************************************************************************/
+static int GetDriverVersion(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    int iErr = U14Status1401(hand, U14_GETDRIVERREVISION, &csBlock);
+    if (iErr == U14ERR_NOERROR)
+        iErr = csBlock.longs[0];
+    return iErr;
+#endif
+#ifdef LINUX
+    return CED_GetDriverRevision(aHand1401[hand]);
+#endif
+}
+
+/****************************************************************************
+** U14MonitorRev
+** Returns the 1401 monitor revision number.
+** The number returned is the minor revision - the part after the
+** decimal point - plus the major revision times 1000.
+*****************************************************************************/
+U14API(int) U14MonitorRev(short hand)
+{
+    int iRev = 0;
+    int iErr = CheckHandle(hand);
+    if (iErr != U14ERR_NOERROR)                 // Check open and in use
+        return iErr;
+
+    if (asType1401[hand] >= U14TYPEPOWER2)      // The Power2 onwards can give us the monitor
+    {                                           //  revision directly for all versions
+        iErr = U14SendString(hand, "INFO,S,28;");
+        if (iErr == U14ERR_NOERROR)
+        {
+            U14LONG lVals[2];                   // Read a single number being the revision
+            iErr = U14LongsFrom1401(hand, lVals, 1);
+            if (iErr > 0)
+            {
+                iErr = U14ERR_NOERROR;
+                iRev = lVals[0];                // This is the minor part of the revision
+                iRev += asType1401[hand] * 10000;
+            }
+        }
+    }
+    else
+    {                                           /* Do it the hard way for older hardware */
+        iErr = U14SendString(hand, ";CLIST;");     /* ask for command levels */
+        if (iErr == U14ERR_NOERROR)
+        {     
+            while (iErr == U14ERR_NOERROR)
+            {
+                char wstr[50];
+                iErr = U14GetString(hand, wstr, 45);
+                if (iErr == U14ERR_NOERROR)
+                {
+                    char *pstr = strstr(wstr,"RESET");  /* Is this the RESET command? */
+                    if ((pstr == wstr) && (wstr[5] == ' '))
+                    {
+                        char *pstr2;
+                        size_t l;
+                        pstr += 6;       /* Move past RESET and followinmg char */
+                        l = strlen(pstr);       /* The length of text remaining */
+                        while (((pstr[l-1] == ' ') || (pstr[l-1] == 13)) && (l > 0))
+                        {
+                            pstr[l-1] = 0;         /* Tidy up string at the end */
+                            l--;                  /* by removing spaces and CRs */
+                        }
+                        pstr2 = strchr(pstr, '.');    /* Find the decimal point */
+                        if (pstr2 != NULL)                /* If we found the DP */
+                        {
+                            *pstr2 = 0;                /* End pstr string at DP */
+                            pstr2++;              /* Now past the decimal point */
+                            iRev = atoi(pstr2);   /* Get the number after point */
+                        }
+                        iRev += (atoi(pstr) * 1000);    /* Add first bit * 1000 */
+                    }
+                    if ((strlen(wstr) < 3) && (wstr[0] == ' '))
+                        break;              /* Spot the last line of results */
+                }
+            }
+        }
+    }
+    if (iErr == U14ERR_NOERROR)            /* Return revision if no error */
+        iErr = iRev;
+
+    return iErr;
+}
+
+/****************************************************************************
+** U14TryToOpen     Tries to open the 1401 number passed
+**  Note : This will succeed with NT driver even if no I/F card or
+**         1401 switched off, so we check state and close the driver
+**         if the state is unsatisfactory in U14Open1401.
+****************************************************************************/
+#ifdef _IS_WINDOWS_
+#define U14NAMEOLD "\\\\.\\CED_140%d"
+#define U14NAMENEW "\\\\.\\CED%d"
+static short U14TryToOpen(int n1401, long* plRetVal, short* psHandle)
+{
+    short sErr = U14ERR_NOERROR;
+    HANDLE hDevice = INVALID_HANDLE_VALUE;
+    DWORD dwErr = 0;
+    int nFirst, nLast, nDev = 0;        /* Used for the search for a 1401 */
+    BOOL bOldName = FALSE;               /* start by looking for a modern driver */
+
+    if (n1401 == 0)                             /* If we need to look for a 1401 */
+    {
+        nFirst = 1;                             /* Set the search range */
+        nLast = MAX1401;                        /* through all the possible 1401s */
+    }
+    else
+        nFirst = nLast = n1401;                 /* Otherwise just one 1401 */
+
+    while (hDevice == INVALID_HANDLE_VALUE)     /* Loop to try for a 1401 */
+    {
+        for (nDev = nFirst; nDev <= nLast; nDev++)
+        {
+            char szDevName[40];                 /* name of the device to open */
+            sprintf(szDevName, bOldName ? U14NAMEOLD : U14NAMENEW, nDev);
+            hDevice = CreateFile(szDevName, GENERIC_WRITE | GENERIC_READ,
+                                 0, 0,          /* Unshared mode does nothing as this is a device */
+                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+            if (hDevice != INVALID_HANDLE_VALUE)/* Check 1401 if opened */
+            {
+                TCSBLOCK csBlock;
+                assert(aHand1401[nDev-1] == INVALID_HANDLE_VALUE);  // assert if already open
+                aHand1401[nDev-1] = hDevice;    /* Save handle for now */
+
+#ifndef _WIN64
+                // Use DIOC method if not windows 9x or if using new device name
+                abUseNTDIOC[nDev-1] = (BOOL)(!bWindows9x || !bOldName);
+#endif
+                sErr = U14Status1401((short)(nDev-1), U14_TYPEOF1401, &csBlock);
+                if (sErr == U14ERR_NOERROR)
+                {
+                    *plRetVal = csBlock.ints[0];
+                    if (csBlock.ints[0] == U14ERR_INUSE)/* Prevent multi opens */
+                    {
+                        CloseHandle(hDevice);   /* treat as open failure */
+                        hDevice = INVALID_HANDLE_VALUE;
+                        aHand1401[nDev-1] = INVALID_HANDLE_VALUE;
+                        sErr = U14ERR_INUSE;
+                    }
+                    else
+                        break;                  /* Exit from for loop on success */
+                }
+                else
+                {
+                    CloseHandle(hDevice);       /* Give up if func fails */
+                    hDevice = INVALID_HANDLE_VALUE;
+                    aHand1401[nDev-1] = INVALID_HANDLE_VALUE;
+                }
+            }
+            else
+            {
+                DWORD dwe = GetLastError();     /* Get error code otherwise */
+                if ((dwe != ERROR_FILE_NOT_FOUND) || (dwErr == 0))
+                    dwErr = dwe;                /* Ignore repeats of 'not found' */
+            }
+        }
+
+        if ((hDevice == INVALID_HANDLE_VALUE) &&/* No device found, and... */
+            (bWindows9x) &&                     /* ...old names are allowed, and... */
+            (bOldName == FALSE))                /* ...not tried old names yet */
+            bOldName = TRUE;                    /* Set flag and go round again */
+        else
+            break;                              /* otherwise that's all folks */
+    }
+
+    if (hDevice != INVALID_HANDLE_VALUE)        /* If we got our device open */
+        *psHandle = (short)(nDev-1);            /* return 1401 number opened */
+    else
+    {
+        if (dwErr == ERROR_FILE_NOT_FOUND)      /* Sort out the error codes */
+            sErr = U14ERR_NO1401DRIV;           /* if file not found */
+        else if (dwErr == ERROR_NOT_SUPPORTED)
+            sErr = U14ERR_DRIVTOOOLD;           /* if DIOC not supported */
+        else if (dwErr == ERROR_ACCESS_DENIED)
+            sErr = U14ERR_INUSE;
+        else
+            sErr = U14ERR_DRIVCOMMS;            /* otherwise assume comms problem */
+    }
+    return sErr;
+}
+#endif
+#ifdef LINUX
+static short U14TryToOpen(int n1401, long* plRetVal, short* psHandle)
+{
+    short sErr = U14ERR_NOERROR;
+    int fh = 0;                             // will be 1401 handle
+    int iErr = 0;
+    int nFirst, nLast, nDev = 0;            // Used for the search for a 1401
+
+    if (n1401 == 0)                         // If we need to look for a 1401
+    {
+        nFirst = 1;                             /* Set the search range */
+        nLast = MAX1401;                        /* through all the possible 1401s */
+    }
+    else
+        nFirst = nLast = n1401;                 /* Otherwise just one 1401 */
+
+    for (nDev = nFirst; nDev <= nLast; nDev++)
+    {
+        char szDevName[40];                 // name of the device to open
+        sprintf(szDevName,"/dev/cedusb/%d", nDev-1);
+        fh = open(szDevName, O_RDWR);       // can only be opened once at a time
+        if (fh > 0)                         // Check 1401 if opened
+        {
+            int iType1401 = CED_TypeOf1401(fh); // get 1401 type
+            aHand1401[nDev-1] = fh;         // Save handle for now
+            if (iType1401 >= 0)
+            {
+                *plRetVal = iType1401;
+                 break;                     // Exit from for loop on success
+            }
+            else
+            {
+                close(fh);                  // Give up if func fails
+                fh = 0;
+                aHand1401[nDev-1] = 0;
+            }
+        }
+        else
+        {
+            if (((errno != ENODEV) && (errno != ENOENT)) || (iErr == 0))
+                iErr = errno;                // Ignore repeats of 'not found'
+        }
+    }
+
+
+    if (fh)                                 // If we got our device open
+        *psHandle = (short)(nDev-1);        // return 1401 number opened
+    else
+    {
+        if ((iErr == ENODEV) || (iErr == ENOENT)) // Sort out the error codes
+            sErr = U14ERR_NO1401DRIV;       // if file not found
+        else if (iErr == EBUSY)
+            sErr = U14ERR_INUSE;
+        else
+            sErr = U14ERR_DRIVCOMMS;        // otherwise assume comms problem
+    }
+
+    return sErr;
+}
+#endif
+/****************************************************************************
+** U14Open1401
+** Tries to get the 1401 for use by this application
+*****************************************************************************/
+U14API(short) U14Open1401(short n1401)
+{
+    long     lRetVal = -1;
+    short    sErr;
+    short    hand = 0;
+    
+    if ((n1401 < 0) || (n1401 > MAX1401))       // must check the 1401 number
+        return U14ERR_BAD1401NUM;
+
+    szLastName[0] = 0;          /* initialise the error info string */
+
+    sErr = U14TryToOpen(n1401, &lRetVal, &hand);
+    if (sErr == U14ERR_NOERROR)
+    {
+        long lDriverVersion = GetDriverVersion(hand);   /* get driver revision */
+        long lDriverRev = -1;
+               if (lDriverVersion >= 0)                    /* can use it if all OK */
+        {
+            lLastDriverType = (lDriverVersion >> 24) & 0x000000FF;
+            asDriverType[hand] = (short)lLastDriverType;    /* Drv type */
+            lLastDriverVersion = lDriverVersion & 0x00FFFFFF;
+            alDriverVersion[hand] = lLastDriverVersion;     /* Actual version */
+            lDriverRev = ((lDriverVersion>>16) & 0x00FF);    /* use hi word */
+        }
+        else
+        {
+            U14Close1401(hand);    /* If there is a problem we should close */
+            return (short)lDriverVersion;      /* and return the error code */
+        }
+    
+        if (lDriverRev < MINDRIVERMAJREV)       /* late enough version?     */
+        {
+            U14Close1401(hand);    /* If there is a problem we should close */
+            return U14ERR_DRIVTOOOLD;           /* too old                  */
+        }
+    
+        asLastRetCode[hand] = U14ERR_NOERROR; /* Initialise this 1401s info */
+        abGrabbed[hand] = FALSE;          /* we are not in single step mode */
+        U14SetTimeout(hand, 3000);      /* set 3 seconds as default timeout */
+
+        switch (lRetVal)
+        {
+        case DRIVRET_STD:  asType1401[hand] = U14TYPE1401; break;      /* Some we do by hand */
+        case DRIVRET_U1401:asType1401[hand] = U14TYPEU1401; break;
+        case DRIVRET_PLUS: asType1401[hand] = U14TYPEPLUS; break;
+        default:  // For the power upwards, we can calculate the codes
+                if ((lRetVal >= DRIVRET_POWER) && (lRetVal <= DRIVRET_MAX))
+                    asType1401[hand] = (short)(lRetVal - (DRIVRET_POWER - U14TYPEPOWER));
+                else
+                    asType1401[hand] = U14TYPEUNKNOWN;
+                break;
+            }
+        U14KillIO1401(hand);                     /* resets the 1401 buffers */
+
+        if (asType1401[hand] != U14TYPEUNKNOWN)   /* If all seems OK so far */
+        {
+            sErr = U14CheckErr(hand);        /* we can check 1401 comms now */
+            if (sErr != 0)                       /* If this failed to go OK */
+                U14Reset1401(hand); /* Reset the 1401 to try to sort it out */
+        }
+
+        sErr = U14StateOf1401(hand);/* Get the state of the 1401 for return */
+        if (sErr == U14ERR_NOERROR)
+            sErr = hand;                 /* return the handle if no problem */
+        else
+            U14Close1401(hand);    /* If there is a problem we should close */
+    }
+
+    return sErr;
+}
+
+
+/****************************************************************************
+** U14Close1401
+** Closes the 1401 so someone else can use it.
+****************************************************************************/
+U14API(short) U14Close1401(short hand)
+{
+    int j;
+    int iAreaMask = 0;                          // Mask for active areas
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)                 // Check open and in use
+        return sErr;
+
+    for (j = 0; j<MAX_TRANSAREAS; ++j)
+    {
+        TGET_TX_BLOCK gtb;
+        int iReturn = U14GetTransfer(hand, &gtb);   // get area information
+        if (iReturn == U14ERR_NOERROR)          // ignore if any problem
+            if (gtb.used)
+                iAreaMask |= (1 << j);          // set a bit for each used area
+    }
+
+    if (iAreaMask)                              // if any areas are in use
+    {
+        U14Reset1401(hand);                     // in case an active transfer running
+        for (j = 0; j < MAX_TRANSAREAS; ++j)    // Locate locked areas
+            if (iAreaMask & (1 << j))           // And kill off any transfers
+                U14UnSetTransfer(hand, (WORD)j);
+    }
+
+#ifdef _IS_WINDOWS_
+    if (aXferEvent[hand])                       // if this 1401 has an open event handle
+    {
+        CloseHandle(aXferEvent[hand]);          // close down the handle
+        aXferEvent[hand] = NULL;                // and mark it as gone
+    }
+
+    if (CloseHandle(aHand1401[hand]))
+#endif
+#ifdef LINUX
+    if (close(aHand1401[hand]) == 0)            // make sure that close works
+#endif
+    {
+        aHand1401[hand] = INVALID_HANDLE_VALUE;
+        asType1401[hand] = U14TYPEUNKNOWN;
+        return U14ERR_NOERROR;
+    }
+    else
+        return U14ERR_BADHAND;     /* BUGBUG GetLastError() ? */
+}
+
+/**************************************************************************
+**
+** Look for open 1401s and attempt to close them down. 32-bit windows only.
+**************************************************************************/
+U14API(void) U14CloseAll(void)
+{
+    int i;
+    for (i = 0; i < MAX1401; i++)       // Tidy up and make safe
+        if (aHand1401[i] != INVALID_HANDLE_VALUE)
+            U14Close1401((short)i);     // Last ditch close 1401
+}
+
+/****************************************************************************
+** U14Reset1401
+** Resets the 1401
+****************************************************************************/
+U14API(short) U14Reset1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    return U14Control1401(hand, U14_RESET1401, &csBlock);
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_Reset1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14ForceReset
+**    Sets the 1401 full reset flag, so that next call to Reset1401 will
+**     always cause a genuine reset.
+*****************************************************************************/
+U14API(short) U14ForceReset(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    return U14Control1401(hand, U14_FULLRESET, &csBlock);
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_FullReset(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14KillIO1401
+**    Removes any pending IO from the buffers.
+*****************************************************************************/
+U14API(short) U14KillIO1401(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    return U14Control1401(hand, U14_KILLIO1401, &csBlock);
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_KillIO1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+
+/****************************************************************************
+** U14SendString
+** Send characters to the 1401
+*****************************************************************************/
+U14API(short) U14SendString(short hand, const char* pString)
+{
+    int nChars;                     // length we are sending
+    long lTimeOutTicks;             // when to time out
+    BOOL bSpaceToSend;              // space to send yet
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+
+    nChars = (int)strlen(pString);  // get string length we want to send
+    if (nChars > MAXSTRLEN)
+        return U14ERR_STRLEN;       // String too long
+
+#ifdef _IS_WINDOWS_
+    // To get here we must wait for the buffer to have some space
+    lTimeOutTicks = U14WhenToTimeOut(hand);
+    do
+    {
+        bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+    }
+    while (!bSpaceToSend && !U14PassedTime(lTimeOutTicks));
+
+    if (!bSpaceToSend)             /* Last-ditch attempt to avoid timeout */
+    {           /* This can happen with anti-virus or network activity! */
+        int i;
+        for (i = 0; (i < 4) && (!bSpaceToSend); ++i)
+        {
+            Sleep(25);       /* Give other threads a chance for a while */
+            bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+        }
+    }
+
+    if (asLastRetCode[hand] == U14ERR_NOERROR)      /* no errors? */
+    {
+        if (bSpaceToSend)
+        {
+            PARAMBLK    rData;
+            DWORD       dwBytes;
+            char        tstr[MAXSTRLEN+5];          /* Buffer for chars */
+
+            if ((hand < 0) || (hand >= MAX1401))
+                sErr = U14ERR_BADHAND;
+            else
+            {
+                strcpy(tstr, pString);              /* Into local buf */
+#ifndef _WIN64
+                if (!USE_NT_DIOC(hand))             /* Using WIN 95 driver access? */
+                {
+                    int iOK = DeviceIoControl(aHand1401[hand], (DWORD)U14_SENDSTRING,
+                                    NULL, 0, tstr, nChars,
+                                    &dwBytes, NULL);
+                    if (iOK)
+                        sErr = (dwBytes >= (DWORD)nChars) ? U14ERR_NOERROR : U14ERR_DRIVCOMMS;
+                    else
+                        sErr = (short)GetLastError();
+                }
+                else
+#endif
+                {
+                    int iOK = DeviceIoControl(aHand1401[hand],(DWORD)U14_SENDSTRING,
+                                    tstr, nChars,
+                                    &rData,sizeof(PARAMBLK),&dwBytes,NULL);
+                    if (iOK && (dwBytes >= sizeof(PARAMBLK)))
+                        sErr = rData.sState;
+                    else
+                        sErr = U14ERR_DRIVCOMMS;
+                }
+
+                if (sErr != U14ERR_NOERROR) // If we have had a comms error
+                    U14ForceReset(hand);    //  make sure we get real reset
+            }
+
+            return sErr;
+
+        }
+        else
+        {
+            U14ForceReset(hand);                //  make sure we get real reset
+            return U14ERR_TIMEOUT;
+        }
+    }
+    else
+        return asLastRetCode[hand];
+#endif
+#ifdef LINUX
+    // Just try to send it and see what happens!
+    sErr = CED_SendString(aHand1401[hand], pString, nChars);
+    if (sErr != U14ERR_NOOUT)       // if any result except "no room in output"...
+    {
+        if (sErr != U14ERR_NOERROR) // if a problem...
+             U14ForceReset(hand);   // ...make sure we get real reset next time
+        return sErr;                // ... we are done as nothing we can do
+    }
+
+    // To get here we must wait for the buffer to have some space
+    lTimeOutTicks = U14WhenToTimeOut(hand);
+    do
+    {
+        bSpaceToSend = (BOOL)((long)U14OutBufSpace(hand) >= nChars);
+        if (!bSpaceToSend)
+            sched_yield();          // let others have fun while we wait
+    }
+    while (!bSpaceToSend && !U14PassedTime(lTimeOutTicks));
+
+    if (asLastRetCode[hand] == U14ERR_NOERROR)                /* no errors? */
+    {
+        if (bSpaceToSend)
+        {
+            sErr = CED_SendString(aHand1401[hand], pString, nChars);
+            if (sErr != U14ERR_NOERROR) // If we have had a comms error
+                U14ForceReset(hand);    //  make sure we get real reset
+            return sErr;
+        }
+        else
+        {
+            U14ForceReset(hand);                //  make sure we get real reset
+            return U14ERR_TIMEOUT;
+        }
+    }
+    else
+        return asLastRetCode[hand];
+#endif
+}
+
+/****************************************************************************
+** U14SendChar
+** Send character to the 1401
+*****************************************************************************/
+U14API(short) U14SendChar(short hand, char cChar)
+{
+#ifdef _IS_WINDOWS_
+    char sz[2]=" ";                         // convert to a string and send
+    sz[0] = cChar;
+    sz[1] = 0;
+    return(U14SendString(hand, sz));        // String routines are better
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_SendChar(aHand1401[hand], cChar) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetString
+** Get a string from the 1401. Returns a null terminated string.
+** The string is all the characters up to the next CR in the buffer
+** or the end of the buffer if that comes first. This only returns text
+** if there is a CR in the buffer. The terminating CR character is removed.
+** wMaxLen  Is the size of the buffer and must be at least 2 or an error.
+** Returns  U14ERR_NOERR if OK with the result in the string or a negative
+**          error code. Any error from the device causes us to set up for
+**          a full reset.
+****************************************************************************/
+U14API(short) U14GetString(short hand, char* pBuffer, WORD wMaxLen)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)             // If an error...
+        return sErr;                        // ...bail out!
+
+#ifdef _IS_WINDOWS_
+    if (wMaxLen>1)                          // we need space for terminating 0
+    {
+        BOOL bLineToGet;                    // true when a line to get
+        long lTimeOutTicks = U14WhenToTimeOut(hand);
+        do
+            bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+        while (!bLineToGet && !U14PassedTime(lTimeOutTicks));
+
+        if (!bLineToGet)             /* Last-ditch attempt to avoid timeout */
+        {           /* This can happen with anti-virus or network activity! */
+            int i;
+            for (i = 0; (i < 4) && (!bLineToGet); ++i)
+            {
+                Sleep(25);       /* Give other threads a chance for a while */
+                bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+            }
+        }
+
+        if (bLineToGet)
+        {
+            if (asLastRetCode[hand] == U14ERR_NOERROR)     /* all ok so far */
+            {
+                DWORD       dwBytes = 0;
+                *((WORD *)pBuffer) = wMaxLen;       /* set up length */
+#ifndef _WIN64
+                if (!USE_NT_DIOC(hand))             /* Win 95 DIOC here ? */
+                {
+                    char tstr[MAXSTRLEN+5];         /* Buffer for Win95 chars */
+                    int iOK;
+
+                    if (wMaxLen > MAXSTRLEN)        /* Truncate length */
+                        wMaxLen = MAXSTRLEN;    
+
+                    *((WORD *)tstr) = wMaxLen;      /* set len */
+
+                    iOK = DeviceIoControl(aHand1401[hand],(DWORD)U14_GETSTRING,
+                                    NULL, 0, tstr, wMaxLen+sizeof(short),
+                                    &dwBytes, NULL);
+                    if (iOK)                        /* Device IO control OK ? */
+                    {
+                        if (dwBytes >= 0)           /* If driver OK */
+                        {
+                            strcpy(pBuffer, tstr);
+                            sErr = U14ERR_NOERROR;
+                        }
+                        else
+                            sErr = U14ERR_DRIVCOMMS;
+                    }
+                    else
+                    {
+                        sErr = (short)GetLastError();
+                        if (sErr > 0)               /* Errors are -ve */
+                            sErr = (short)-sErr;
+                    }
+                }
+                else
+#endif
+                {       /* Here for NT, the DLL must own the buffer */
+                    HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE,wMaxLen+sizeof(short));
+                    if (hMem)
+                    {
+                        char* pMem = (char*)GlobalLock(hMem);
+                        if (pMem)
+                        {
+                            int iOK = DeviceIoControl(aHand1401[hand],(DWORD)U14_GETSTRING,
+                                            NULL, 0, pMem, wMaxLen+sizeof(short),
+                                            &dwBytes, NULL);
+                            if (iOK)                /* Device IO control OK ? */
+                            {
+                                if (dwBytes >= wMaxLen)
+                                {
+                                    strcpy(pBuffer, pMem+sizeof(short));
+                                    sErr = *((SHORT*)pMem);
+                                }
+                                else
+                                    sErr = U14ERR_DRIVCOMMS;
+                            }
+                            else
+                                sErr = U14ERR_DRIVCOMMS;
+
+                            GlobalUnlock(hMem);
+                        }
+                        else
+                            sErr = U14ERR_OUTOFMEMORY;
+
+                        GlobalFree(hMem);
+                    }
+                    else
+                        sErr = U14ERR_OUTOFMEMORY;
+                }
+
+                if (sErr == U14ERR_NOERROR)     // If all OK...
+                    TranslateString(pBuffer);   // ...convert any commas to spaces
+                else                            // If we have had a comms error...
+                    U14ForceReset(hand);        // ...make sure we get real reset
+
+            }
+            else
+                sErr = asLastRetCode[hand];
+        }
+        else
+        {
+            sErr = U14ERR_TIMEOUT;
+            U14ForceReset(hand);            //  make sure we get real reset
+        }
+    }
+    else
+        sErr = U14ERR_BUFF_SMALL;
+    return sErr;
+#endif
+#ifdef LINUX
+    if (wMaxLen>1)                          // we need space for terminating 0
+    {
+        BOOL bLineToGet;                    // true when a line to get
+        long lTimeOutTicks = U14WhenToTimeOut(hand);
+        do
+        {
+            bLineToGet = (BOOL)(U14LineCount(hand) != 0);
+            if (!bLineToGet)
+                sched_yield();
+
+        }
+        while (!bLineToGet && !U14PassedTime(lTimeOutTicks));
+
+        if (bLineToGet)
+        {
+            sErr = CED_GetString(aHand1401[hand], pBuffer, wMaxLen-1);   // space for terminator
+            if (sErr >=0)                    // if we were OK...
+            {
+                if (sErr >= wMaxLen)         // this should NOT happen unless
+                    sErr = U14ERR_DRIVCOMMS; // ...driver Comms are very bad
+                else
+                {
+                    pBuffer[sErr] = 0;      // OK, so terminate the string...
+                    TranslateString(pBuffer);  // ...and convert commas to spaces.
+                }
+            }
+
+            if (sErr < U14ERR_NOERROR)       // If we have had a comms error
+                U14ForceReset(hand);            //  make sure we get real reset
+        }
+        else
+        {
+            sErr = U14ERR_TIMEOUT;
+            U14ForceReset(hand);            //  make sure we get real reset
+        }
+    }
+    else
+        sErr = U14ERR_BUFF_SMALL;
+
+    return sErr >= U14ERR_NOERROR ? U14ERR_NOERROR : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetChar
+** Get a character from the 1401. CR returned as CR.
+*****************************************************************************/
+U14API(short) U14GetChar(short hand, char* pcChar)
+{
+#ifdef _IS_WINDOWS_
+    char sz[2];                             // read a very short string
+    short sErr = U14GetString(hand, sz, 2); // read one char and nul terminate it
+    *pcChar = sz[0];    // copy to result, NB char translate done by GetString
+    if (sErr == U14ERR_NOERROR)
+    {                                       // undo translate of CR to zero
+        if (*pcChar == '\0')                // by converting back
+            *pcChar = '\n';                 // What a nasty thing to have to do
+    }
+    return sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)             // Check parameters
+        return sErr;
+    sErr = CED_GetChar(aHand1401[hand]);    // get one char, if available
+    if (sErr >= 0)
+    {
+        *pcChar = (char)sErr;              // return if it we have one
+        return U14ERR_NOERROR;              // say all OK
+    }
+    else
+        return sErr;
+#endif
+}
+
+/****************************************************************************
+** U14Stat1401
+** Returns 0 for no lines or error or non zero for something waiting
+****************************************************************************/
+U14API(short) U14Stat1401(short hand)
+{
+    return ((short)(U14LineCount(hand) > 0));
+}
+
+/****************************************************************************
+** U14CharCount
+** Returns the number of characters in the input buffer
+*****************************************************************************/
+U14API(short) U14CharCount(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_STAT1401, &csBlock);
+    if (sErr == U14ERR_NOERROR)
+        sErr = csBlock.ints[0];
+    return sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_Stat1401(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14LineCount
+** Returns the number of CR characters in the input buffer
+*****************************************************************************/
+U14API(short) U14LineCount(short hand)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14Status1401(hand, U14_LINECOUNT, &csBlock);
+    if (sErr == U14ERR_NOERROR)
+        sErr = csBlock.ints[0];
+    return sErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_LineCount(aHand1401[hand]) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14GetErrorString
+** Converts error code supplied to a decent descriptive string.
+** NOTE: This function may use some extra information stored
+**       internally in the DLL. This information is stored on a
+**       per-process basis, but it might be altered if you call
+**       other functions after getting an error and before using
+**       this function.
+****************************************************************************/
+U14API(void)  U14GetErrorString(short nErr, char* pStr, WORD wMax)
+{
+    char    wstr[150];
+
+    switch (nErr)              /* Basically, we do this with a switch block */
+    {
+    case U14ERR_OFF:
+        sprintf(wstr, "The 1401 is apparently switched off (code %d)", nErr);
+        break;
+
+    case U14ERR_NC:
+        sprintf(wstr, "The 1401 is not connected to the interface card (code %d)", nErr);
+        break;
+
+    case U14ERR_ILL:
+        sprintf(wstr, "The 1401 is not working correctly (code %d)", nErr);
+        break;
+
+    case U14ERR_NOIF:
+        sprintf(wstr, "The 1401 interface card was not detected (code %d)", nErr);
+        break;
+
+    case U14ERR_TIME:
+        sprintf(wstr, "The 1401 fails to become ready for use (code %d)", nErr);
+        break;
+
+    case U14ERR_BADSW:
+        sprintf(wstr, "The 1401 interface card jumpers are incorrect (code %d)", nErr);
+        break;
+
+    case U14ERR_NOINT:
+        sprintf(wstr, "The 1401 interrupt is not available for use (code %d)", nErr);
+        break;
+
+    case U14ERR_INUSE:
+        sprintf(wstr, "The 1401 is already in use by another program (code %d)", nErr);
+        break;
+
+    case U14ERR_NODMA:
+        sprintf(wstr, "The 1401 DMA channel is not available for use (code %d)", nErr);
+        break;
+
+    case U14ERR_BADHAND:
+        sprintf(wstr, "The application supplied an incorrect 1401 handle (code %d)", nErr);
+        break;
+
+    case U14ERR_BAD1401NUM:
+        sprintf(wstr, "The application used an incorrect 1401 number (code %d)", nErr);
+        break;
+
+    case U14ERR_NO_SUCH_FN:
+        sprintf(wstr, "The code passed to the 1401 driver is invalid (code %d)", nErr);
+        break;
+
+    case U14ERR_NO_SUCH_SUBFN:
+        sprintf(wstr, "The sub-code passed to the 1401 driver is invalid (code %d)", nErr);
+        break;
+
+    case U14ERR_NOOUT:
+        sprintf(wstr, "No room in buffer for characters for the 1401 (code %d)", nErr);
+        break;
+
+    case U14ERR_NOIN:
+        sprintf(wstr, "No characters from the 1401 are available (code %d)", nErr);
+        break;
+
+    case U14ERR_STRLEN:
+        sprintf(wstr, "A string sent to or read from the 1401 was too long (code %d)", nErr);
+        break;
+
+    case U14ERR_LOCKFAIL:
+        sprintf(wstr, "Failed to lock host memory for data transfer (code %d)", nErr);
+        break;
+
+    case U14ERR_UNLOCKFAIL:
+        sprintf(wstr, "Failed to unlock host memory after data transfer (code %d)", nErr);
+        break;
+
+    case U14ERR_ALREADYSET:
+        sprintf(wstr, "The transfer area used is already set up (code %d)", nErr);
+        break;
+
+    case U14ERR_NOTSET:
+        sprintf(wstr, "The transfer area used has not been set up (code %d)", nErr);
+        break;
+
+    case U14ERR_BADAREA:
+        sprintf(wstr, "The transfer area number is incorrect (code %d)", nErr);
+        break;
+
+    case U14ERR_NOFILE:
+        sprintf(wstr, "The command file %s could not be opened (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_READERR:
+        sprintf(wstr, "The command file %s could not be read (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_UNKNOWN:
+        sprintf(wstr, "The %s command resource could not be found (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_HOSTSPACE:
+        sprintf(wstr, "Unable to allocate memory for loading command %s (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_LOCKERR:
+        sprintf(wstr, "Unable to lock memory for loading command %s (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_CLOADERR:
+        sprintf(wstr, "Error in loading command %s, bad command format (code %d)", szLastName, nErr);
+        break;
+
+    case U14ERR_TOXXXERR:
+        sprintf(wstr, "Error detected after data transfer to or from the 1401 (code %d)", nErr);
+        break;
+
+    case U14ERR_NO386ENH:
+        sprintf(wstr, "Windows 3.1 is not running in 386 enhanced mode (code %d)", nErr);
+        break;
+
+    case U14ERR_NO1401DRIV:
+        sprintf(wstr, "The 1401 device driver cannot be found (code %d)\nUSB:   check plugged in and powered\nOther: not installed?", nErr);
+        break;
+
+    case U14ERR_DRIVTOOOLD:
+        sprintf(wstr, "The 1401 device driver is too old for use (code %d)", nErr);
+        break;
+
+    case U14ERR_TIMEOUT:
+        sprintf(wstr, "Character transmissions to the 1401 timed-out (code %d)", nErr);
+        break;
+
+    case U14ERR_BUFF_SMALL:
+        sprintf(wstr, "Buffer for text from the 1401 was too small (code %d)", nErr);
+        break;
+
+    case U14ERR_CBALREADY:
+        sprintf(wstr, "1401 monitor callback already set up (code %d)", nErr);
+        break;
+
+    case U14ERR_BADDEREG:
+        sprintf(wstr, "1401 monitor callback deregister invalid (code %d)", nErr);
+        break;
+
+    case U14ERR_DRIVCOMMS:
+        sprintf(wstr, "1401 device driver communications failed (code %d)", nErr);
+        break;
+
+    case U14ERR_OUTOFMEMORY:
+        sprintf(wstr, "Failed to allocate or lock memory for text from the 1401 (code %d)", nErr);
+        break;
+
+    default:
+        sprintf(wstr, "1401 error code %d returned; this code is unknown", nErr);
+        break;
+
+    }
+    if ((WORD)strlen(wstr) >= wMax-1)  /* Check for string being too long */
+        wstr[wMax-1] = 0;                          /* and truncate it if so */
+    strcpy(pStr, wstr);                       /* Return the error string */
+}
+
+/***************************************************************************
+** U14GetTransfer
+** Get a TGET_TX_BLOCK describing a transfer area (held in the block)
+***************************************************************************/
+U14API(short) U14GetTransfer(short hand, TGET_TX_BLOCK *pTransBlock)
+{
+    short sErr = CheckHandle(hand);
+#ifdef _IS_WINDOWS_
+    if (sErr == U14ERR_NOERROR)
+    { 
+        DWORD dwBytes = 0;
+        BOOL bOK = DeviceIoControl(aHand1401[hand], (DWORD)U14_GETTRANSFER, NULL, 0, pTransBlock,
+                              sizeof(TGET_TX_BLOCK), &dwBytes, NULL);
+    
+        if (bOK && (dwBytes >= sizeof(TGET_TX_BLOCK)))
+            sErr = U14ERR_NOERROR;
+        else
+            sErr = U14ERR_DRIVCOMMS;
+    }
+    return sErr;
+#endif
+#ifdef LINUX
+    return (sErr == U14ERR_NOERROR) ? CED_GetTransfer(aHand1401[hand], pTransBlock) : sErr;
+#endif
+}
+/////////////////////////////////////////////////////////////////////////////
+// U14WorkingSet
+// For Win32 only, adjusts process working set so that minimum is at least
+//  dwMinKb and maximum is at least dwMaxKb.
+// Return value is zero if all went OK, or a code from 1 to 3 indicating the
+//  cause of the failure:
+//
+//     1 unable to access process (insufficient rights?)
+//     2 unable to read process working set
+//     3 unable to set process working set - bad parameters?
+U14API(short) U14WorkingSet(DWORD dwMinKb, DWORD dwMaxKb)
+{
+#ifdef _IS_WINDOWS_
+    short sRetVal = 0;                      // 0 means all is OK
+    HANDLE hProcess;
+    DWORD dwVer = GetVersion();
+       if (dwVer & 0x80000000)                 // is this not NT?
+        return 0;                           // then give up right now
+
+    // Now attempt to get information on working set size
+    hProcess = OpenProcess(STANDARD_RIGHTS_REQUIRED |
+                                  PROCESS_QUERY_INFORMATION |
+                                  PROCESS_SET_QUOTA,
+                                  FALSE, _getpid());
+    if (hProcess)
+    {
+        SIZE_T dwMinSize,dwMaxSize;
+        if (GetProcessWorkingSetSize(hProcess, &dwMinSize, &dwMaxSize))
+        {
+            DWORD dwMin = dwMinKb << 10;    // convert from kb to bytes
+            DWORD dwMax = dwMaxKb << 10;
+
+            // if we get here, we have managed to read the current size
+            if (dwMin > dwMinSize)          // need to change sizes?
+                dwMinSize = dwMin;
+
+            if (dwMax > dwMaxSize)
+                dwMaxSize = dwMax;
+
+            if (!SetProcessWorkingSetSize(hProcess, dwMinSize, dwMaxSize))
+                sRetVal = 3;                // failed to change size
+        }
+        else
+            sRetVal = 2;                    // failed to read original size
+
+        CloseHandle(hProcess);
+    }
+    else
+        sRetVal = 1;            // failed to get handle
+
+    return sRetVal;
+#endif
+#ifdef LINUX
+    if (dwMinKb | dwMaxKb)
+    {
+        // to stop compiler moaning
+    }
+    return U14ERR_NOERROR;
+#endif
+}
+
+/****************************************************************************
+** U14UnSetTransfer  Cancels a transfer area
+** wArea    The index of a block previously used in by SetTransfer
+*****************************************************************************/
+U14API(short) U14UnSetTransfer(short hand, WORD wArea)
+{
+    short sErr = CheckHandle(hand);
+#ifdef _IS_WINDOWS_
+    if (sErr == U14ERR_NOERROR)
+    {
+       TCSBLOCK csBlock;
+       csBlock.ints[0] = (short)wArea;       /* Area number into control block */
+       sErr = U14Control1401(hand, U14_UNSETTRANSFER, &csBlock);  /* Free area */
+   
+       VirtualUnlock(apAreas[hand][wArea], auAreas[hand][wArea]);/* Unlock */
+       apAreas[hand][wArea] = NULL;                         /* Clear locations */
+       auAreas[hand][wArea] = 0;
+    }
+    return sErr;
+#endif
+#ifdef LINUX
+    return (sErr == U14ERR_NOERROR) ? CED_UnsetTransfer(aHand1401[hand], wArea) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14SetTransArea      Sets an area up to be used for transfers
+** WORD  wArea     The area number to set up
+** void *pvBuff    The address of the buffer for the data.
+** DWORD dwLength  The length of the buffer for the data
+** short eSz       The element size (used for byte swapping on the Mac)
+****************************************************************************/
+U14API(short) U14SetTransArea(short hand, WORD wArea, void *pvBuff,
+                                          DWORD dwLength, short eSz)
+{
+    TRANSFERDESC td;
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+    if (wArea >= MAX_TRANSAREAS)                    // Is this a valid area number
+        return U14ERR_BADAREA;
+
+#ifdef _IS_WINDOWS_
+    assert(apAreas[hand][wArea] == NULL);
+    assert(auAreas[hand][wArea] == 0);
+
+    apAreas[hand][wArea] = pvBuff;                  /* Save data for later */
+    auAreas[hand][wArea] = dwLength;
+
+    if (!VirtualLock(pvBuff, dwLength))             /* Lock using WIN32 calls */
+    {
+        apAreas[hand][wArea] = NULL;                /* Clear locations */
+        auAreas[hand][wArea] = 0;
+        return U14ERR_LOCKERR;                      /* VirtualLock failed */
+    }
+#ifndef _WIN64
+    if (!USE_NT_DIOC(hand))                         /* Use Win 9x DIOC? */
+    {
+        DWORD dwBytes;
+        VXTRANSFERDESC vxDesc;                      /* Structure to pass to VXD */
+        vxDesc.wArea = wArea;                       /* Copy across simple params */
+        vxDesc.dwLength = dwLength;
+
+        // Check we are not asking an old driver for more than area 0
+        if ((wArea != 0) && (U14DriverVersion(hand) < 0x00010002L))
+            sErr = U14ERR_DRIVTOOOLD;
+        else
+        {
+            vxDesc.dwAddrOfs = (DWORD)pvBuff;       /* 32 bit offset */
+            vxDesc.wAddrSel  = 0;
+
+            if (DeviceIoControl(aHand1401[hand], (DWORD)U14_SETTRANSFER,
+                                pvBuff,dwLength,    /* Will translate pointer */
+                                &vxDesc,sizeof(VXTRANSFERDESC),
+                                &dwBytes,NULL))
+            {
+                if (dwBytes >= sizeof(VXTRANSFERDESC)) /* Driver OK ? */
+                    sErr = U14ERR_NOERROR;
+                else
+                    sErr = U14ERR_DRIVCOMMS;        /* Else never got there */
+            }
+            else
+                sErr = (short)GetLastError();
+        }
+    }
+    else
+#endif
+    {
+        PARAMBLK rWork;
+        DWORD dwBytes;
+        td.wArea = wArea;     /* Pure NT - put data into struct */
+        td.lpvBuff = pvBuff;
+        td.dwLength = dwLength;
+        td.eSize = 0;                // Dummy element size
+
+        if (DeviceIoControl(aHand1401[hand],(DWORD)U14_SETTRANSFER,
+                            &td,sizeof(TRANSFERDESC),
+                            &rWork,sizeof(PARAMBLK),&dwBytes,NULL))
+        {
+            if (dwBytes >= sizeof(PARAMBLK))    // maybe error from driver?
+                sErr = rWork.sState;            // will report any error
+            else
+                sErr = U14ERR_DRIVCOMMS;        // Else never got there
+        }
+        else
+            sErr = U14ERR_DRIVCOMMS;
+    }
+
+    if (sErr != U14ERR_NOERROR)
+    {
+        if (sErr != U14ERR_LOCKERR)             // unless lock failed...
+            VirtualUnlock(pvBuff, dwLength);    // ...release the lock
+        apAreas[hand][wArea] = NULL;            // Clear locations
+        auAreas[hand][wArea] = 0;
+    }
+
+    return sErr;
+#endif
+#ifdef LINUX
+    // The strange cast is so that it works in 64 and 32-bit linux as long is 64-bits
+    // in the 64 bit version.
+    td.lpvBuff = (long long)((unsigned long)pvBuff);
+    td.wAreaNum = wArea;
+    td.dwLength = dwLength;
+    td.eSize = eSz;                // Dummy element size
+    return CED_SetTransfer(aHand1401[hand], &td);
+#endif
+}
+
+/****************************************************************************
+** U14SetTransferEvent  Sets an event for notification of application
+** wArea       The tranfer area index, from 0 to MAXAREAS-1
+**    bEvent      True to create an event, false to remove it
+**    bToHost     Set 0 for notification on to1401 tranfers, 1 for
+**                notification of transfers to the host PC
+**    dwStart     The offset of the sub-area of interest
+**    dwLength    The size of the sub-area of interest
+**
+** The device driver will set the event supplied to the signalled state
+** whenever a DMA transfer to/from the specified area is completed. The
+** transfer has to be in the direction specified by bToHost, and overlap
+** that part of the whole transfer area specified by dwStart and dwLength.
+** It is important that this function is called with bEvent false to release
+** the event once 1401 activity is finished.
+**
+** Returns 1 if an event handle exists, 0 if all OK and no event handle or
+** a negative code for an error.
+****************************************************************************/
+U14API(short) U14SetTransferEvent(short hand, WORD wArea, BOOL bEvent,
+                                  BOOL bToHost, DWORD dwStart, DWORD dwLength)
+{
+#ifdef _IS_WINDOWS_
+    TCSBLOCK csBlock;
+    short sErr = U14TransferFlags(hand);        // see if we can handle events
+    if (sErr >= U14ERR_NOERROR)                 // check handle is OK
+    {
+        bEvent = bEvent && ((sErr & U14TF_NOTIFY) != 0); // remove request if we cannot do events
+        if (wArea >= MAX_TRANSAREAS)            // Check a valid area...
+            return U14ERR_BADAREA;              // ...and bail of not
+
+        // We can hold an event for each area, so see if we need to change the
+        // state of the event.
+        if ((bEvent != 0) != (aXferEvent[hand] != 0))    // change of event state?
+        {
+            if (bEvent)                         // want one and none present
+                aXferEvent[hand] = CreateEvent(NULL, FALSE, FALSE, NULL);
+            else
+            {
+                CloseHandle(aXferEvent[hand]);  // clear the existing event
+                aXferEvent[hand] = NULL;        // and clear handle
+            }
+        }
+
+        // We have to store the parameters differently for 64-bit operations
+        //  because a handle is 64 bits long. The drivers know of this and
+        //  handle the information appropriately.
+#ifdef _WIN64
+        csBlock.longs[0] = wArea;               // Pass paramaters into the driver...
+        if (bToHost != 0)                       // The direction flag is held in the
+            csBlock.longs[0] |= 0x10000;        //  upper word of the transfer area value
+        *((HANDLE*)&csBlock.longs[1]) = aXferEvent[hand];  // The event handle is 64-bits
+        csBlock.longs[3] = dwStart;             // Thankfully these two remain
+        csBlock.longs[4] = dwLength;            //  as unsigned 32-bit values
+#else
+        csBlock.longs[0] = wArea;               // pass paramaters into the driver...
+        csBlock.longs[1] = (long)aXferEvent[hand];    // ...especially the event handle
+        csBlock.longs[2] = bToHost;
+        csBlock.longs[3] = dwStart;
+        csBlock.longs[4] = dwLength;
+#endif
+        sErr = U14Control1401(hand, U14_SETTRANSEVENT, &csBlock);
+        if (sErr == U14ERR_NOERROR)
+            sErr = (short)(aXferEvent[hand] != NULL);    // report if we have a flag
+    }
+
+    return sErr;
+#endif
+#ifdef LINUX
+    TRANSFEREVENT te;
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+
+    if (wArea >= MAX_TRANSAREAS)            // Is this a valid area number
+        return U14ERR_BADAREA;
+
+    te.wAreaNum = wArea;                    // copy parameters to the control block
+    te.wFlags = bToHost ? 1 : 0;            // bit 0 sets the direction
+    te.dwStart = dwStart;                   // start offset of the event area
+    te.dwLength = dwLength;                 // size of the event area
+    te.iSetEvent = bEvent;                  // in Windows, this creates/destroys the event
+    return CED_SetEvent(aHand1401[hand], &te);
+#endif
+}
+
+/****************************************************************************
+** U14TestTransferEvent
+** Would a U14WaitTransferEvent() call return immediately? return 1 if so,
+** 0 if not or a negative code if a problem.
+****************************************************************************/
+U14API(int) U14TestTransferEvent(short hand, WORD wArea)
+{
+#ifdef _IS_WINDOWS_
+    int iErr = CheckHandle(hand);
+    if (iErr == U14ERR_NOERROR)
+    {
+        if (aXferEvent[hand])           // if a handle is set...
+            iErr = WaitForSingleObject(aXferEvent[hand], 0) == WAIT_OBJECT_0;
+    }
+    return iErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_TestEvent(aHand1401[hand], wArea) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14WaitTransferEvent
+** Wait for a transfer event with a timeout.
+** msTimeOut is 0 for an infinite wait, else it is the maximum time to wait
+**           in milliseconds in range 0-0x00ffffff.
+** Returns   If no event handle then return immediately. Else return 1 if
+**           timed out or 0=event, and a negative code if a problem.
+****************************************************************************/
+U14API(int) U14WaitTransferEvent(short hand, WORD wArea, int msTimeOut)
+{
+#ifdef _IS_WINDOWS_
+    int iErr = CheckHandle(hand);
+    if (iErr == U14ERR_NOERROR)
+    {
+        if (aXferEvent[hand])
+        {
+            if (msTimeOut == 0)
+                msTimeOut = INFINITE;
+            iErr = WaitForSingleObject(aXferEvent[hand], msTimeOut) != WAIT_OBJECT_0;
+        }
+        else
+            iErr = TRUE;                // say we timed out if no event
+    }
+    return iErr;
+#endif
+#ifdef LINUX
+    short sErr = CheckHandle(hand);
+    return (sErr == U14ERR_NOERROR) ? CED_WaitEvent(aHand1401[hand], wArea, msTimeOut) : sErr;
+#endif
+}
+
+/****************************************************************************
+** U14SetCircular    Sets an area up for circular DMA transfers
+** WORD  wArea          The area number to set up
+** BOOL  bToHost        Sets the direction of data transfer
+** void *pvBuff        The address of the buffer for the data
+** DWORD dwLength       The length of the buffer for the data
+****************************************************************************/
+U14API(short) U14SetCircular(short hand, WORD wArea, BOOL bToHost,
+                                                                       void *pvBuff, DWORD dwLength)
+{
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+
+    if (wArea >= MAX_TRANSAREAS)         /* Is this a valid area number */
+        return U14ERR_BADAREA;
+
+       if (!bToHost)             /* For now, support tohost transfers only */
+        return U14ERR_BADAREA;            /* best error code I can find */
+#ifdef _IS_WINDOWS_
+    assert(apAreas[hand][wArea] == NULL);
+    assert(auAreas[hand][wArea] == 0);
+
+    apAreas[hand][wArea] = pvBuff;              /* Save data for later */
+    auAreas[hand][wArea] = dwLength;
+
+    if (!VirtualLock(pvBuff, dwLength))      /* Lock using WIN32 calls */
+        sErr = U14ERR_LOCKERR;                    /* VirtualLock failed */
+    else
+    {
+        PARAMBLK rWork;
+        DWORD dwBytes;
+        TRANSFERDESC txDesc;
+        txDesc.wArea = wArea;             /* Pure NT - put data into struct */
+        txDesc.lpvBuff = pvBuff;
+        txDesc.dwLength = dwLength;
+        txDesc.eSize = (short)bToHost;       /* Use this for direction flag */
+   
+        if (DeviceIoControl(aHand1401[hand],(DWORD)U14_SETCIRCULAR,
+                           &txDesc, sizeof(TRANSFERDESC),
+                           &rWork, sizeof(PARAMBLK),&dwBytes,NULL))
+        {
+           if (dwBytes >= sizeof(PARAMBLK))          /* error from driver? */
+               sErr = rWork.sState;         /* No, just return driver data */
+           else
+               sErr = U14ERR_DRIVCOMMS;            /* Else never got there */
+        }
+        else
+            sErr = U14ERR_DRIVCOMMS;
+    }
+
+    if (sErr != U14ERR_NOERROR)
+    {
+        if (sErr != U14ERR_LOCKERR)
+            VirtualUnlock(pvBuff, dwLength);         /* Release NT lock */
+        apAreas[hand][wArea] = NULL;                 /* Clear locations */
+        auAreas[hand][wArea] = 0;
+    }
+
+    return sErr;
+#endif
+#ifdef LINUX
+    else
+    {
+        TRANSFERDESC td;
+        td.lpvBuff = (long long)((unsigned long)pvBuff);
+        td.wAreaNum = wArea;
+        td.dwLength = dwLength;
+        td.eSize = (short)bToHost;       /* Use this for direction flag */
+        return CED_SetCircular(aHand1401[hand], &td);
+    }
+#endif
+}
+
+/****************************************************************************
+** Function  GetCircBlk returns the size (& start offset) of the next
+**           available block of circular data.
+****************************************************************************/
+U14API(int) U14GetCircBlk(short hand, WORD wArea, DWORD *pdwOffs)
+{
+    int lErr = CheckHandle(hand);
+    if (lErr != U14ERR_NOERROR)
+        return lErr;
+
+    if (wArea >= MAX_TRANSAREAS)            // Is this a valid area number?
+        return U14ERR_BADAREA;
+    else
+    {
+#ifdef _IS_WINDOWS_
+        PARAMBLK rWork;
+        TCSBLOCK csBlock;
+        DWORD dwBytes;
+        csBlock.longs[0] = wArea;               // Area number into control block
+        rWork.sState = U14ERR_DRIVCOMMS;
+        if (DeviceIoControl(aHand1401[hand], (DWORD)U14_GETCIRCBLK, &csBlock, sizeof(TCSBLOCK), &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+           (dwBytes >= sizeof(PARAMBLK)))
+            lErr = rWork.sState;
+        else
+            lErr = U14ERR_DRIVCOMMS;
+   
+        if (lErr == U14ERR_NOERROR)             // Did everything go OK?
+        {                                       // Yes, we can pass the results back
+            lErr = rWork.csBlock.longs[1];      // Return the block information
+            *pdwOffs = rWork.csBlock.longs[0];  // Offset is first in array
+        }
+#endif
+#ifdef LINUX
+        TCIRCBLOCK cb;
+        cb.nArea = wArea;                       // Area number into control block
+        cb.dwOffset = 0;
+        cb.dwSize = 0;
+        lErr = CED_GetCircBlock(aHand1401[hand], &cb);
+        if (lErr == U14ERR_NOERROR)             // Did everything go OK?
+        {                                       // Yes, we can pass the results back
+            lErr = cb.dwSize;                   // return the size
+            *pdwOffs = cb.dwOffset;             // and the offset
+        }
+#endif
+    }
+    return lErr;
+}
+
+/****************************************************************************
+** Function  FreeCircBlk marks the specified area of memory as free for
+**           resuse for circular transfers and returns the size (& start
+**           offset) of the next available block of circular data.
+****************************************************************************/
+U14API(int) U14FreeCircBlk(short hand, WORD wArea, DWORD dwOffs, DWORD dwSize,
+                                        DWORD *pdwOffs)
+{
+    int lErr = CheckHandle(hand);
+    if (lErr != U14ERR_NOERROR)
+        return lErr;
+
+    if (wArea < MAX_TRANSAREAS)                 // Is this a valid area number
+    {
+#ifdef _IS_WINDOWS_
+        PARAMBLK rWork;
+        TCSBLOCK csBlock;
+        DWORD dwBytes;
+        csBlock.longs[0] = wArea;               // Area number into control block
+        csBlock.longs[1] = dwOffs;
+        csBlock.longs[2] = dwSize;
+        rWork.sState = U14ERR_DRIVCOMMS;
+        if (DeviceIoControl(aHand1401[hand], (DWORD)U14_FREECIRCBLK, &csBlock, sizeof(TCSBLOCK),
+                           &rWork, sizeof(PARAMBLK), &dwBytes, NULL) &&
+           (dwBytes >= sizeof(PARAMBLK)))
+           lErr = rWork.sState;
+        else
+           lErr = U14ERR_DRIVCOMMS;
+       if (lErr == U14ERR_NOERROR)             // Did everything work OK?
+       {                                       // Yes, we can pass the results back
+           lErr = rWork.csBlock.longs[1];      // Return the block information
+           *pdwOffs = rWork.csBlock.longs[0];  // Offset is first in array
+       }
+#endif
+#ifdef LINUX
+        TCIRCBLOCK cb;
+        cb.nArea = wArea;                       // Area number into control block
+        cb.dwOffset = dwOffs;
+        cb.dwSize = dwSize;
+    
+        lErr = CED_FreeCircBlock(aHand1401[hand], &cb);
+        if (lErr == U14ERR_NOERROR)             // Did everything work OK?
+        {                                       // Yes, we can pass the results back
+            lErr = cb.dwSize;                   // Return the block information
+            *pdwOffs = cb.dwOffset;             // Offset is first in array
+        }
+#endif
+    }
+    else
+        lErr = U14ERR_BADAREA;
+
+    return lErr;
+}
+
+/****************************************************************************
+** Transfer
+** Transfer moves data to 1401 or to host
+** Assumes memory is allocated and locked,
+** which it should be to get a pointer
+*****************************************************************************/
+static short Transfer(short hand, BOOL bTo1401, char* pData,
+                       DWORD dwSize, DWORD dw1401, short eSz)
+{
+    char strcopy[MAXSTRLEN+1];          // to hold copy of work string
+    short sResult = U14SetTransArea(hand, 0, (void *)pData, dwSize, eSz);
+    if (sResult == U14ERR_NOERROR)      // no error
+    {
+        sprintf(strcopy,                // data offset is always 0
+                "TO%s,$%X,$%X,0;", bTo1401 ? "1401" : "HOST", dw1401, dwSize);
+
+        U14SendString(hand, strcopy);   // send transfer string
+
+        sResult = U14CheckErr(hand);    // Use ERR command to check for done
+        if (sResult > 0)
+            sResult = U14ERR_TOXXXERR;  // If a 1401 error, use this code
+
+        U14UnSetTransfer(hand, 0);
+    }
+    return sResult;
+}
+
+/****************************************************************************
+** Function  ToHost transfers data into the host from the 1401
+****************************************************************************/
+U14API(short) U14ToHost(short hand, char* pAddrHost, DWORD dwSize,
+                                            DWORD dw1401, short eSz)
+{
+    short sErr = CheckHandle(hand);
+    if ((sErr == U14ERR_NOERROR) && dwSize) // TOHOST is a constant
+        sErr = Transfer(hand, TOHOST, pAddrHost, dwSize, dw1401, eSz);
+    return sErr;
+}
+
+/****************************************************************************
+** Function  To1401 transfers data into the 1401 from the host
+****************************************************************************/
+U14API(short) U14To1401(short hand, const char* pAddrHost,DWORD dwSize,
+                                    DWORD dw1401, short eSz)
+{
+    short sErr = CheckHandle(hand);
+    if ((sErr == U14ERR_NOERROR) && dwSize) // TO1401 is a constant
+        sErr = Transfer(hand, TO1401, (char*)pAddrHost, dwSize, dw1401, eSz);
+    return sErr;
+}
+
+/****************************************************************************
+** Function  LdCmd    Loads a command from a full path or just a file
+*****************************************************************************/
+#ifdef _IS_WINDOWS_
+#define file_exist(name) (_access(name, 0) != -1)
+#define file_open(name) _lopen(name, OF_READ)
+#define file_close(h)   _lclose(h)
+#define file_seek(h, pos) _llseek(h, pos, FILE_BEGIN) 
+#define file_read(h, buffer, size) (_lread(h, buffer, size) == size)
+#endif
+#ifdef LINUX
+#define file_exist(name) (access(name, F_OK) != -1)
+#define file_open(name) open(name, O_RDONLY)
+#define file_close(h)   close(h)
+#define file_seek(h, pos) lseek(h, pos, SEEK_SET) 
+#define file_read(h, buffer, size) (read(h, buffer, size) == (ssize_t)size)
+static DWORD GetModuleFileName(void* dummy, char* buffer, int max)
+{
+    // The following works for Linux systems with a /proc file system.
+    char szProcPath[32];
+    sprintf(szProcPath, "/proc/%d/exe", getpid());  // attempt to read link
+    if (readlink(szProcPath, buffer, max) != -1)
+    {
+        dirname (buffer);
+        strcat  (buffer, "/");
+        return strlen(buffer);
+    }
+    return 0;
+}
+#endif
+
+U14API(short) U14LdCmd(short hand, const char* command)
+{
+    char strcopy[MAXSTRLEN+1];      // to hold copy of work string
+    BOOL bGotIt = FALSE;            // have we found the command file?
+    int iFHandle;                   // file handle of command
+#define FNSZ 260
+    char filnam[FNSZ];              // space to build name in
+    char szCmd[25];                 // just the command name with extension
+
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+
+    if (strchr(command, '.') != NULL)       // see if we have full name
+    {
+        if (file_exist(command))            // If the file exists
+        {
+            strcpy(filnam, command);        // use name as is
+            bGotIt = TRUE;                  // Flag no more searching
+        }
+        else                                // not found, get file name for search
+        {
+            char* pStr = strrchr(command, PATHSEP);  // Point to last separator
+            if (pStr != NULL)               // Check we got it
+            {
+                pStr++;                     // move past the backslash
+                strcpy(szCmd, pStr);        // copy file name as is
+            }
+            else
+                strcpy(szCmd, command);     // use as is
+        }
+    }
+    else    // File extension not supplied, so build the command file name
+    {
+        char szExt[8];
+        strcpy(szCmd, command);             // Build command file name
+        ExtForType(asType1401[hand], szExt);// File extension string
+        strcat(szCmd, szExt);               // add it to the end
+    }
+
+    // Next place to look is in the 1401 folder in the same place as the
+    // application was run from.
+    if (!bGotIt)                            // Still not got it?
+    {
+        DWORD dwLen = GetModuleFileName(NULL, filnam, FNSZ); // Get app path
+        if (dwLen > 0)                      // and use it as path if found
+        {
+            char* pStr = strrchr(filnam, PATHSEP);    // Point to last separator
+            if (pStr != NULL)
+            {
+                *(++pStr) = 0;                  // Terminate string there
+                if (strlen(filnam) < FNSZ-6)    // make sure we have space
+                {
+                    strcat(filnam, "1401" PATHSEPSTR);  // add in 1401 subdir
+                    strcat(filnam,szCmd);
+                    bGotIt = (BOOL)file_exist(filnam);  // See if file exists
+                }
+            }
+        }
+    }
+
+    // Next place to look is in whatever path is set by the 1401DIR environment
+    // variable, if it exists.
+    if (!bGotIt)                            // Need to do more searches?/
+    {
+        char* pStr = getenv("1401DIR");     // Try to find environment var
+        if (pStr != NULL)                   // and use it as path if found
+        {
+            strcpy(filnam, pStr);                   // Use path in environment
+            if (filnam[strlen(filnam)-1] != PATHSEP)// We need separator
+                strcat(filnam, PATHSEPSTR);
+            strcat(filnam, szCmd);
+            bGotIt = (BOOL)file_exist(filnam); // Got this one?
+        }
+    }
+
+    // Last place to look is the default location.
+    if (!bGotIt)                        // Need to do more searches?
+    {
+        strcpy(filnam, DEFCMDPATH);     // Use default path
+        strcat(filnam, szCmd);
+        bGotIt = file_exist(filnam);    // Got this one?
+    }
+
+    iFHandle = file_open(filnam);
+    if (iFHandle == -1)
+        sErr = U14ERR_NOFILE;
+    else
+    {                                   // first read in the header block
+        CMDHEAD rCmdHead;               // to hold the command header
+        if (file_read(iFHandle, &rCmdHead, sizeof(CMDHEAD)))
+        {
+            size_t nComSize = rCmdHead.wCmdSize;
+            char* pMem = malloc(nComSize);
+            if (pMem != NULL)
+            {
+                file_seek(iFHandle, sizeof(CMDHEAD));
+                if (file_read(iFHandle, pMem, (UINT)nComSize))
+                {
+                    sErr = U14SetTransArea(hand, 0, (void *)pMem, (DWORD)nComSize, ESZBYTES);
+                    if (sErr == U14ERR_NOERROR)
+                    {
+                        sprintf(strcopy, "CLOAD,0,$%X;", (int)nComSize);
+                        sErr = U14SendString(hand, strcopy);
+                        if (sErr == U14ERR_NOERROR)
+                        {
+                            sErr = U14CheckErr(hand);     // Use ERR to check for done
+                            if (sErr > 0)
+                                sErr = U14ERR_CLOADERR;   // If an error, this code
+                        }
+                        U14UnSetTransfer(hand, 0);  // release transfer area
+                    }
+                }
+                else
+                    sErr = U14ERR_READERR;
+                free(pMem);
+            }
+            else
+                sErr = U14ERR_HOSTSPACE;    // memory allocate failed
+        }
+        else
+            sErr = U14ERR_READERR;
+
+        file_close(iFHandle);               // close the file
+    }
+
+    return sErr;
+}
+
+
+/****************************************************************************
+** Ld
+** Loads a command into the 1401
+** Returns NOERROR code or a long with error in lo word and index of
+** command that failed in high word
+****************************************************************************/
+U14API(DWORD) U14Ld(short hand, const char* vl, const char* str)
+{
+    DWORD dwIndex = 0;              // index to current command
+    long lErr = U14ERR_NOERROR;     // what the error was that went wrong
+    char strcopy[MAXSTRLEN+1];      // stores unmodified str parameter
+    char szFExt[8];                 // The command file extension
+    short sErr = CheckHandle(hand);
+    if (sErr != U14ERR_NOERROR)
+        return sErr;
+
+    ExtForType(asType1401[hand], szFExt);   // File extension string
+    strcpy(strcopy, str);               // to avoid changing original
+
+    // now break out one command at a time and see if loaded
+    if (*str)                           // if anything there
+    {
+        BOOL bDone = FALSE;             // true when finished all commands
+        int iLoop1 = 0;                 // Point at start of string for command name
+        int iLoop2 = 0;                 // and at start of str parameter
+        do                              // repeat until end of str
+        {
+            char filnam[MAXSTRLEN+1];   // filename to use
+            char szFName[MAXSTRLEN+1];  // filename work string
+
+            if (!strcopy[iLoop1])       // at the end of the string?
+                bDone = TRUE;           // set the finish flag
+
+            if (bDone || (strcopy[iLoop1] == ','))  // end of cmd?
+            {
+                U14LONG er[5];                  // Used to read back error results
+                ++dwIndex;                      // Keep count of command number, first is 1
+                szFName[iLoop2]=(char)0;        // null terminate name of command
+
+                strncpy(szLastName, szFName, sizeof(szLastName));    // Save for error info
+                szLastName[sizeof(szLastName)-1] = 0;
+                strncat(szLastName, szFExt, sizeof(szLastName));     // with extension included
+                szLastName[sizeof(szLastName)-1] = 0;
+
+                U14SendString(hand, szFName);   // ask if loaded
+                U14SendString(hand, ";ERR;");   // add err return
+
+                lErr = U14LongsFrom1401(hand, er, 5);
+                if (lErr > 0)
+                {
+                    lErr = U14ERR_NOERROR;
+                    if (er[0] == 255)           // if command not loaded at all
+                    {
+                        if (vl && *vl)          // if we have a path name
+                        {
+                            strcpy(filnam, vl);
+                            if (strchr("\\/:", filnam[strlen(filnam)-1]) == NULL)
+                                strcat(filnam, PATHSEPSTR); // add separator if none found
+                            strcat(filnam, szFName);    // add the file name
+                            strcat(filnam, szFExt);     // and extension
+                        }
+                        else
+                            strcpy(filnam, szFName);    // simple name
+
+                        lErr = U14LdCmd(hand, filnam);  // load cmd
+                        if (lErr != U14ERR_NOERROR)     // spot any errors
+                            bDone = TRUE;               // give up if an error
+                    }
+                }
+                else
+                    bDone = TRUE;       // give up if an error
+
+                iLoop2 = 0;             // Reset pointer to command name string
+                ++iLoop1;               // and move on through str parameter
+            }
+            else
+                szFName[iLoop2++] = strcopy[iLoop1++];  // no command end, so copy 1 char
+        }
+        while (!bDone);
+    }
+
+    if (lErr == U14ERR_NOERROR)
+    {
+        szLastName[0] = 0;      // No error, so clean out command name here
+        return lErr;
+    }
+    else
+        return ((dwIndex<<16) | ((DWORD)lErr & 0x0000FFFF));
+}
+
+// Initialise the library (if not initialised) and return the library version
+U14API(int) U14InitLib(void)
+{
+    int iRetVal = U14LIB_VERSION;
+    if (iAttached == 0)         // only do this the first time please
+    {
+        int i;
+#ifdef _IS_WINDOWS_
+        int j;
+        DWORD   dwVersion = GetVersion();
+        bWindows9x = FALSE;                  // Assume not Win9x
+
+        if (dwVersion & 0x80000000)                 // if not windows NT
+        {
+            if ((LOBYTE(LOWORD(dwVersion)) < 4) &&  // if Win32s or...
+                 (HIBYTE(LOWORD(dwVersion)) < 95))  // ...below Windows 95
+            iRetVal = 0;                            // We do not support this
+        else
+            bWindows9x = TRUE;                      // Flag we have Win9x
+        }
+#endif
+        
+        for (i = 0; i < MAX1401; i++)               // initialise the device area
+        {
+            aHand1401[i] = INVALID_HANDLE_VALUE;    // Clear handle values
+            asType1401[i] = U14TYPEUNKNOWN;         // and 1401 type codes
+            alTimeOutPeriod[i] = 3000;              // 3 second timeouts
+#ifdef _IS_WINDOWS_
+#ifndef _WIN64
+            abUseNTDIOC[i] = (BOOL)!bWindows9x;
+#endif
+            aXferEvent[i] = NULL;                   // there are no Xfer events
+            for (j = 0; j < MAX_TRANSAREAS; j++)    // Clear out locked area info
+            {
+                apAreas[i][j] = NULL;
+                auAreas[i][j] = 0;
+            }
+#endif
+        }
+    }
+    return iRetVal;
+}
+
+///--------------------------------------------------------------------------------
+/// Functions called when the library is loaded and unloaded to give us a chance to
+/// setup the library.
+
+
+#ifdef _IS_WINDOWS_
+#ifndef U14_NOT_DLL
+/****************************************************************************
+** FUNCTION: DllMain(HANDLE, DWORD, LPVOID)
+** LibMain is called by Windows when the DLL is initialized, Thread Attached,
+** and other times. Refer to SDK documentation, as to the different ways this
+** may be called.
+****************************************************************************/
+INT APIENTRY DllMain(HANDLE hInst, DWORD ul_reason_being_called, LPVOID lpReserved)
+{
+    int iRetVal = 1;
+
+    switch (ul_reason_being_called)
+    {
+    case DLL_PROCESS_ATTACH:
+        iRetVal = U14InitLib() > 0;         // does nothing if iAttached != 0
+        ++iAttached;                        // count times attached
+        break;
+
+    case DLL_PROCESS_DETACH:
+        if (--iAttached == 0)               // last man out?
+            U14CloseAll();                  // release all open handles
+        break;
+    }
+    return iRetVal;
+
+    UNREFERENCED_PARAMETER(lpReserved);
+}
+#endif
+#endif
+#ifdef LINUX
+void __attribute__((constructor)) use1401_load(void)
+{
+    U14InitLib();
+    ++iAttached;
+}
+
+void __attribute__((destructor)) use1401_unload(void)
+{
+        if (--iAttached == 0)               // last man out?
+            U14CloseAll();                  // release all open handles
+}
+#endif