//{ **************************************************************************** }
//{ FileName............: READWRITE.CPP                                          }
//{ Project.............: FLEXCOP                                                }
//{ Author(s)...........: M.Majoor                                               }
//{ Original contents...: Walter Oney ('Programming the Windows Driver Model')   }
//{ Version.............: 2.00                                                   }
//{ ---------------------------------------------------------------------------- }
//{ Read/Write request processors for FLEXCOP driver.                            }
//{                                                                              }
//{ Version  Date      Comment                                                   }
//{ 1.00     20050410  - Initial release based on SAA7146A driver V2.02          }
//{ 2.00     20060722  - No changes                                              }
//{ **************************************************************************** }


#include "stddcls.h"
#include "driver.h"


//{ **************************************************************************** }
//{ Descript : Forwards.                                                         }
//{ **************************************************************************** }
VOID                 OnCancelReadWrite(PDEVICE_OBJECT fdo, PIRP Irp);
IO_ALLOCATION_ACTION AdapterControl   (PDEVICE_OBJECT fdo, PIRP Irp, PVOID regbase, PDEVICE_EXTENSION pdx);


//{ **************************************************************************** }
//{ Params   : <fdo>      Functional device object                               }
//{            <junk>     I/O request packet                                     }
//{            <regbase>  Registry base key                                      }
//{            <pdx>      Device extension                                       }
//{ Return   : <Result>   I/O allocation                                         }
//{                                                                              }
//{ Descript : Adapter control.                                                  }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
IO_ALLOCATION_ACTION AdapterControl(PDEVICE_OBJECT fdo, PIRP junk, PVOID regbase, PDEVICE_EXTENSION pdx)
{
  // The second argument <junk> to AdapterControl is whatever was in the CurrentIrp
  // field of the device object when you called AllocateAdapterChannel. When you
  // use a DEVQUEUE for IRP queuing, you need to ask the DEVQUEUE object what IRP
  // is current. If you use the standard model, wherein IoStartPacket and
  // IoStartNextPacket manage the queue, junk would be the right IRP. In that case,
  // it would have been named Irp instead.
  PIRP Irp    = GetCurrentIrp(&pdx->dqReadWrite);
  PMDL mdl    = Irp->MdlAddress;
  // There are few differences between code to handle input and output operations
  // using DMA, so it's often convenient to handle both operations in a single
  // subroutine. This line of code examines the major function code for the IRP
  // to decide whether a read or write is occurring.
  BOOLEAN isread = IoGetCurrentIrpStackLocation(Irp)->MajorFunction == IRP_MJ_READ;
  // The regbase argument to this function is an opaque handle that identifies
  // the set of map registers that have been reserved for your use during this
  // operation. You'll need this value later, so you should save it in your device extension.
  pdx->regbase = regbase;
  pdx->busy    = TRUE;
  // KeFlushIoBuffers makes sure that the contents of all processor memory caches
  // for the memory buffer you're using are flushed to memory. The third argument
  // (TRUE) indicates that you're flushing the cache in preparation for a DMA operation.
  // The CPU architecture might require this step because, in general, DMA operations
  // proceed directly to or from memory without necessarily involving the caches.
  KeFlushIoBuffers(mdl, isread, TRUE);
  // The MapTransfer routine programs the DMA hardware for one stage of a transfer
  // and returns the physical address where the transfer should start. Notice that
  // you supply the address of an MDL as the second argument to this function. Since
  // you need an MDL at this point, you would ordinarily have opted for the DO_DIRECT_IO
  // buffering method when you first created your device object, and the I/O Manager
  // would therefore have automatically created the MDL for you. You also pass along
  // the map register base address (regbase). You indicate which portion of the MDL
  // is involved in this stage of the operation by supplying a virtual address
  // (pdx->vaddr) and a byte count (pdx->xfer). MapTransfer will use the virtual
  // address argument to calculate an offset into the buffer area, from which it
  // can determine the physical page numbers containing your data.
  PHYSICAL_ADDRESS address = (*pdx->AdapterObject->DmaOperations->MapTransfer)
                             (pdx->AdapterObject, mdl, regbase, pdx->vaddr, &pdx->xfer, !isread);

  // This is the point at which you program your hardware in the device-specific
  // way that is required. You might, for example, use one of the WRITE_Xxx HAL
  // routines to send the physical address and byte count values to registers on
  // your card, and you might thereafter strobe some command register to begin transferring data.

  // TODO start transfer running on the device using the physical buffer
  // address we just got back from MapTransfer

  // We return the constant DeallocateObjectKeepRegisters to indicate that we're
  // done using the adapter object but are still using the map registers. In this
  // particular case (PCI bus master), there will never be any contention for the
  // adapter object in the first place, so it hardly matters that we've released
  // the adapter object. In other bus-mastering situations, though, we might be
  // sharing a DMA controller with other devices. Releasing the adapter object
  // allows those other devices to begin transfers by using a disjoint set of map
  // registers from the ones we're still using.
  return DeallocateObjectKeepRegisters;
}


//{ **************************************************************************** }
//{ Params   : <fdo>     Functional device object                                }
//{            <Irp>     I/O request packet                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Dispatch cleanup IRP_MJ_CLEANUP.                                  }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS DispatchCleanup(PDEVICE_OBJECT fdo, PIRP Irp)
{
  PAGED_CODE();
  KdPrint((DRIVERNAME " - IRP_MJ_CLEANUP\n"));
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  // TODO If you use multiple queues, code this instead:
  //	CleanupAllRequests(pdx->queues, arraysize(pdx->queues), stack->FileObject, STATUS_CANCELLED);
  CleanupRequests(&pdx->dqReadWrite, stack->FileObject, STATUS_CANCELLED);
  CleanupControlRequests(pdx, STATUS_CANCELLED, stack->FileObject);
  return CompleteRequest(Irp, STATUS_SUCCESS, 0);
}


//{ **************************************************************************** }
//{ Params   : <fdo>     Functional device object                                }
//{            <Irp>     I/O request packet                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Dispatch create IRP_MJ_CREATE.                                    }
//{ Notes    : You should also acquire the remove lock when you successfully     }
//{            process an IRP_MJ_CREATE. In contrast to the other situations     }
//{            we've considered, you don't release the lock before returning     }
//{            from the DispatchCreate routine. The balancing call to            }
//{            IoReleaseRemoveLock occurs instead in the dispatch routine for    }
//{            IRP_MJ_CLOSE. In other words, you hold the remove lock for the    }
//{            entire time something has a handle open to your device.           }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS DispatchCreate(PDEVICE_OBJECT fdo, PIRP Irp)
{
  PAGED_CODE();
  KdPrint((DRIVERNAME " - IRP_MJ_CREATE\n"));
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

  NTSTATUS status = STATUS_SUCCESS;
  if (NT_SUCCESS(status))
  {                                                        // If okay to open
    if (InterlockedIncrement(&pdx->handles) == 1)
    {                                                      // First open handle
//      OpenHandle(pdx, stack->FileObject);
    }
  }
  return CompleteRequest(Irp, status, 0);
}


//{ **************************************************************************** }
//{ Params   : <fdo>     Functional device object                                }
//{            <Irp>     I/O request packet                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Dispatch close IRP_MJ_CLOSE.                                      }
//{ Notes    : See note on <DispatchCreate>                                      }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS DispatchClose(PDEVICE_OBJECT fdo, PIRP Irp)
{
  PAGED_CODE();
  KdPrint((DRIVERNAME " - IRP_MJ_CLOSE\n"));
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

//  if (pdx->pevent)                                         // If there is a global event allocated
//    DeregisterEventGlobal(pdx);                            // Remove it
  if (InterlockedDecrement(&pdx->handles) == 0)
  {                                                        // If no more open handles
//    DeregisterEvent(pdx, stack->FileObject);
//    CloseHandle(pdx, stack->FileObject);
  }
  return CompleteRequest(Irp, STATUS_SUCCESS, 0);
}


// TODO If you use separate queues for reads and writes, you need to arrange to
// call StartPacket and CancelRequests with the right queue argument. The easiest
// way to do that is to have separate dispatch and cancel functions for IRP_MJ_READ and
// IRP_MJ_WRITE.


//{ **************************************************************************** }
//{ Params   : <fdo>     Functional device object                                }
//{            <Irp>     I/O request packet                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Dispatch read/write IRP_MJ_READ/WRITE.                            }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp)
{
  PAGED_CODE();
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

  IoMarkIrpPending(Irp);
  StartPacket(&pdx->dqReadWrite, fdo, Irp, OnCancelReadWrite);
  return STATUS_PENDING;
}


//{ **************************************************************************** }
//{ Params   : <fdo>     Functional device object                                }
//{            <Irp>     I/O request packet                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : On cancel callback read/write.                                    }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
VOID OnCancelReadWrite(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  CancelRequest(&pdx->dqReadWrite, Irp);
}


// TODO If you use separate queues for reads and writes, you will either need
// separate DPC objects for each, or else you'll need to distinguish in this DPC
// routine between the different kinds of IRP so you can reference the correct
// queue in calls to functions like GetCurrentIrp and StartNextPacket.


//{ **************************************************************************** }
//{ Params   : <Dpc>   Deferred procedure call                                   }
//{            <fdo>   Functional device object                                  }
//{            <junk>  I/O request packet                                        }
//{            <pdx>   Device extension                                          }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Deferred procedure call for interrupt service routine.            }
//{ Notes    : DMA stuff 'disabled'                                              }
//{            Now handles uncached IRP (notification)                           }
//{ **************************************************************************** }
#pragma LOCKEDCODE
VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT fdo, PIRP junk, PDEVICE_EXTENSION pdx)
{
  PIRP nfyirp = UncacheControlRequest(pdx, &pdx->NotifyIrp);
  if (nfyirp)                                              // If still valid notification
  {
    CompleteRequest(nfyirp, STATUS_SUCCESS, 0);            // Complete the cached IRP request
  }

//  NTSTATUS status;
//  PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
//  status = Irp->IoStatus.Status;                           // Get ending status left by ISR
//  BOOLEAN isread = IoGetCurrentIrpStackLocation(Irp)->MajorFunction == IRP_MJ_READ;
//  PMDL mdl = Irp->MdlAddress;

//  (*pdx->AdapterObject->DmaOperations->FlushAdapterBuffers)
//    (pdx->AdapterObject, mdl, pdx->regbase, pdx->vaddr, pdx->xfer, !isread);

//  pdx->nbytes -= pdx->xfer;
//  pdx->numxfer += pdx->xfer;

//  if (pdx->nbytes && NT_SUCCESS(status))
//  {                                                        // If start next stage of transfer
//    pdx->vaddr = (PVOID) ((PUCHAR) pdx->vaddr + pdx->xfer);
//    pdx->xfer = pdx->nbytes;
//    ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr, pdx->nbytes);
//    if (nregs > pdx->nMapRegistersAllocated)
//    {
//      nregs = pdx->nMapRegistersAllocated;
//      pdx->xfer = nregs * PAGE_SIZE;
//    }
//    PHYSICAL_ADDRESS address =(*pdx->AdapterObject->DmaOperations->MapTransfer)
//                                (pdx->AdapterObject, mdl, pdx->regbase, pdx->vaddr, &pdx->xfer, !isread);

    // TODO Start the next stage of the operation running on the device
//  }
//  else
//  {                                                        // If request is complete
//    (*pdx->AdapterObject->DmaOperations->FreeMapRegisters)
//      (pdx->AdapterObject, pdx->regbase, pdx->nMapRegistersAllocated);
//    StartNextPacket(&pdx->dqReadWrite, fdo);
//    IoCompleteRequest(Irp, IO_NO_INCREMENT);
//    IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
//  }
}


//{ **************************************************************************** }
//{ Params   : <InterruptObject>  Interrupt object                               }
//{            <pdx>              Device extension                               }
//{ Return   : <Result>           TRUE  if handled                               }
//{                               FALSE if not for us                            }
//{                                                                              }
//{ Descript : On interrupt.                                                     }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject, PDEVICE_EXTENSION pdx)
{
  BOOLEAN UseNotifyIrp = FALSE;
  UCHAR   IrqBufferIndex;
  UCHAR   FifoBufferIndex;
  ULONG   SigData;
  ULONG   AccessingAddress;

  // Our first task is to discover whether our own device is trying to interrupt now.
  // We should check for all pending causes of interrupts. If none, we return immediately.

  // If our device is NOT interrupting, return immediate
  ULONG isrstatus = READ_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_ISR));
  if (isrstatus == 0)
    return FALSE;

  pdx->ISR = isrstatus;

  // Check all bits
  for (IrqBufferIndex = 0; IrqBufferIndex < IRQBUFFERS; IrqBufferIndex++)
  {
    // Check individual bit
    if ((isrstatus & 1) != 0)
    {
      pdx->irqBuffers[IrqBufferIndex].irqsReceived++;
      // Interrupt must be active
      if (pdx->irqBuffers[IrqBufferIndex].active)
      {
        pdx->irqBuffers[IrqBufferIndex].irqsActiveReceived++;
        // FIFO handling (we do this before handling signalling)
        if (pdx->irqBuffers[IrqBufferIndex].fifoActive)
        {
          // Advance to the next buffer to use and check for overflows
          pdx->irqBuffers[IrqBufferIndex].fifoBufferPrevious++;
          if (pdx->irqBuffers[IrqBufferIndex].fifoBufferPrevious > pdx->irqBuffers[IrqBufferIndex].fifoBufferLast)
          {
            pdx->irqBuffers[IrqBufferIndex].fifoBufferPrevious = pdx->irqBuffers[IrqBufferIndex].fifoBufferFirst;
            pdx->irqBuffers[IrqBufferIndex].fifoBufferCirculated++;
          }
          FifoBufferIndex = pdx->irqBuffers[IrqBufferIndex].fifoBufferPrevious;
          if (pdx->fifoBuffers[FifoBufferIndex].bufferId >= 0)
          {
            // If we are dealing with a sub buffer interrupt (IRQ 0..3) then we must decide which
            // sub buffer is in use (so we can copy the other sub buffer)
            // Note: Since there should no other interrupts using this mechanism it would be strange
            //       that the interrupt is actually > 3. Nevertheless >3 is not handled here
            // Check for DMA1 interrupt (subbuffer or packet interrupt)
            if (IrqBufferIndex < 2)
            {
              if (pdx->Dma1SubLargest != 0)
              {
                AccessingAddress = READ_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_DMA1ADDRESS));
                if (AccessingAddress >= pdx->Dma1SubLargest)
                  RtlCopyMemory(pdx->fifoBuffers[FifoBufferIndex].vaBuffer,
                                pdx->fifoBuffers[FifoBufferIndex].vaBufferSource[pdx->Dma1SubLargestOrder[0]],
                                pdx->fifoBuffers[FifoBufferIndex].bufferSize);
                else
                  RtlCopyMemory(pdx->fifoBuffers[FifoBufferIndex].vaBuffer,
                                pdx->fifoBuffers[FifoBufferIndex].vaBufferSource[pdx->Dma1SubLargestOrder[1]],
                                pdx->fifoBuffers[FifoBufferIndex].bufferSize);
              }
            }
            else
              // Check for DMA2 interrupt (subbuffer or packet interrupt)
              if (IrqBufferIndex < 4)
              {
                if (pdx->Dma2SubLargest != 0)
                {
                  AccessingAddress = READ_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_DMA2ADDRESS));
                  if (AccessingAddress >= pdx->Dma2SubLargest)
                    RtlCopyMemory(pdx->fifoBuffers[FifoBufferIndex].vaBuffer,
                                  pdx->fifoBuffers[FifoBufferIndex].vaBufferSource[pdx->Dma2SubLargestOrder[0]],
                                  pdx->fifoBuffers[FifoBufferIndex].bufferSize);
                  else
                    RtlCopyMemory(pdx->fifoBuffers[FifoBufferIndex].vaBuffer,
                                  pdx->fifoBuffers[FifoBufferIndex].vaBufferSource[pdx->Dma2SubLargestOrder[1]],
                                  pdx->fifoBuffers[FifoBufferIndex].bufferSize);
                }
              }
            // Check for overflow on buffer
            // Overflows are marked on different locations:
            // . the FIFO buffer itself
            // . the IRQ which uses the FIFO buffer
            // . the global overflow counter
            if (pdx->fifoBuffers[FifoBufferIndex].writeTag == TRUE)
            {
              pdx->fifoOverflows++;
              pdx->irqBuffers[IrqBufferIndex].fifoOverflows++;
              pdx->fifoBuffers[FifoBufferIndex].bufferOverflows++;
            }
            pdx->fifoBuffers[FifoBufferIndex].writeTag = TRUE;
            pdx->fifoBuffers[FifoBufferIndex].bufferIrqsActiveReceived = pdx->irqBuffers[IrqBufferIndex].irqsActiveReceived;
          }
        }
        // EVENT handling
        if (pdx->irqBuffers[IrqBufferIndex].eventActive)
          UseNotifyIrp = TRUE;
      }
    }
    isrstatus >>= 1;                                       // Next bit
  }

  // We have to go through all of it again for the signalling. We do this after
  // clearing the interrupt status.
  isrstatus = pdx->ISR;
  for (IrqBufferIndex = 0; IrqBufferIndex < IRQBUFFERS; IrqBufferIndex++)
  {
    // Check individual bit
    if ((isrstatus & 1) != 0)
    {
      // Interrupt must be active
      if (pdx->irqBuffers[IrqBufferIndex].active)
      {
        // SIG handling
        if (pdx->irqBuffers[IrqBufferIndex].signalActive)
        {
          SigData = READ_REGISTER_ULONG((PULONG) (pdx->membase + pdx->irqBuffers[IrqBufferIndex].signalRegister));
          SigData &= pdx->irqBuffers[IrqBufferIndex].signalAnd;
          SigData |= pdx->irqBuffers[IrqBufferIndex].signalOr;
          SigData ^= pdx->irqBuffers[IrqBufferIndex].signalXor;
          WRITE_REGISTER_ULONG((PULONG) (pdx->membase + pdx->irqBuffers[IrqBufferIndex].signalRegister), SigData);
        }
      }
    }
    isrstatus >>= 1;                                       // Next bit
  }

// HERE WE PUT THE CODE FOR INTERRUPT HANDLING
//  -> fifoActive
//     check if the FIFO buffer of <fifoBuffer> is available (BufferId >=0)
//     ..handle it


  // Check for spurious interrupt.

  // Adjust our interrupt counter
  pdx->InterruptCount++;                                   // Increment our interrupt counter (no need to interlock)
  if (pdx->InterruptCount > 10000000)
    pdx->InterruptCount = 0;


// Note: the global event signalling was originally implemented but only gave a
//       throughput of about 9 times/sec! Not sure why, but we are now using IRP
//       notification instead (meaning that an IRP is waiting for it to be completed)
// SignalEventGlobal(pdx);                                // Signal global event (if any)

//  if (!pdx->busy)
//    return TRUE;

// When we use a DEVQUEUE, we rely on the queue object to keep track of the current IRP.
// This interrupt might be one that we don't expect because we're not currently servicing
// any IRP. In that case, we still have to clear the interrupt but shouldn't do anything else.
//  PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
//  ASSERT(Irp);

  // Is there a pending notification (cached) IRP we should call?
  // Must be enabled by IRQ handling
  if (pdx->NotifyIrp && UseNotifyIrp)
  {
    // Let the DPC complete the cached IRP request
    IoRequestDpc(pdx->DeviceObject, NULL, pdx);
    return TRUE;
  }

  // It's also possible that a Plug and Play or power event has occurred that will
  // cause any new IRPs to be rejected by the dispatch routine. The DEVQUEUE's
  // AreRequestsBeingAborted function tells us that fact so that we can abort the
  // current request right now. Aborting an active request is a reasonable thing
  // to do with a device such as this that proceeds byte by byte. Similarly it's a
  // good idea to check whether the IRP has been cancelled if it will take a long
  // time to finish the IRP. If your device interrupts only when it's done with a
  // long transfer, you could leave this test out of your ISR.
//  NTSTATUS status;
//  if (Irp->Cancel)
//    status = STATUS_CANCELLED;
//  else
//    status = AreRequestsBeingAborted(&pdx->dqReadWrite);

  // As we progressed through the preceding code, we set the Boolean dpc variable
  // to TRUE if a DPC is now appropriate to complete the current IRP.
//  if (!NT_SUCCESS(status))
//  {                                                        // If to terminate request
//    pdx->busy = FALSE;
//    Irp->IoStatus.Status = status;
//    Irp->IoStatus.Information = 0;
//    IoRequestDpc(pdx->DeviceObject, NULL, pdx);
//    return TRUE;
//  }

  // We are now logically complete, request a DPC
//  Irp->IoStatus.Status = STATUS_SUCCESS;
//  Irp->IoStatus.Information = 0;
//  pdx->busy = FALSE;
//  IoRequestDpc(pdx->DeviceObject, NULL, pdx);
  return TRUE;
}


//{ **************************************************************************** }
//{ Params   : <fdo>         Functional device object                            }
//{            <raw>         Untranslated partial resource list                  }
//{            <translated>  Translated partial resource list                    }
//{ Return   : <Result>      NTSTATUS                                            }
//{                                                                              }
//{ Descript : Start a single device.                                            }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS StartDevice(PDEVICE_OBJECT fdo, PCM_PARTIAL_RESOURCE_LIST raw, PCM_PARTIAL_RESOURCE_LIST translated)
{
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  NTSTATUS status;

  // Identify the I/O resources we're supposed to use.
  ULONG vector;
  KIRQL irql;
  KINTERRUPT_MODE mode;
  KAFFINITY affinity;
  BOOLEAN irqshare;
  BOOLEAN gotinterrupt = FALSE;

  PHYSICAL_ADDRESS membase;
  BOOLEAN gotmemory = FALSE;

  if (!translated)
    return STATUS_DEVICE_CONFIGURATION_ERROR;              // No resources assigned??

  PCM_PARTIAL_RESOURCE_DESCRIPTOR resource = translated->PartialDescriptors;
  ULONG nres = translated->Count;
  for (ULONG i = 0; i < nres; ++i, ++resource)
  {                                                        // For each resource
    switch (resource->Type)
    {                                                      // Switch on resource type
      case CmResourceTypeInterrupt:
        irql = (KIRQL) resource->u.Interrupt.Level;
        vector = resource->u.Interrupt.Vector;
        affinity = resource->u.Interrupt.Affinity;
        mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)
                 ? Latched : LevelSensitive;
        irqshare = resource->ShareDisposition == CmResourceShareShared;
        gotinterrupt = TRUE;
        break;
      case CmResourceTypeMemory:
        membase = resource->u.Memory.Start;
        pdx->memsize = resource->u.Memory.Length;
        gotmemory = TRUE;
        break;
      case CmResourceTypeDevicePrivate:
        // Reserved system resource
//        KdPrint((DRIVERNAME " - Resource type device private (reserved for system use)\n"));
        break;
      default:
        KdPrint((DRIVERNAME " - Unexpected I/O resource type %d\n", resource->Type));
        break;
    }
  }

  // Verify that we got all the resources we were expecting
  if (!(TRUE
      && gotinterrupt
      && gotmemory
     ))
  {
    KdPrint((DRIVERNAME " - Didn't get expected I/O resources\n"));
    return STATUS_DEVICE_CONFIGURATION_ERROR;
  }

  // Determine bus type before proceeding, just so we don't have a bunch of cleanup
  // to do if this call fails
  INTERFACE_TYPE bustype;
  ULONG junk;
  status = IoGetDeviceProperty(pdx->Pdo, DevicePropertyLegacyBusType, sizeof(bustype),
                               &bustype, &junk);
  if (!NT_SUCCESS(status))
  {
    KdPrint((DRIVERNAME " - IoGetDeviceProperty failed - %X\n", status));
    return status;
  }

  pdx->membase = (PUCHAR) MmMapIoSpace(membase, pdx->memsize, MmNonCached);
  if (!pdx->membase)
  {
    KdPrint((DRIVERNAME " - Unable to map memory block %I64X, length %X\n", membase, pdx->memsize));
    return STATUS_INSUFFICIENT_RESOURCES;
  }

  // Configure a DMA adapter object
  DEVICE_DESCRIPTION dd;
  RtlZeroMemory(&dd, sizeof(dd));
  dd.Version = DEVICE_DESCRIPTION_VERSION;
  dd.InterfaceType = bustype;
  dd.MaximumLength = COMMONBUFFERSIZE;
  dd.Dma32BitAddresses = TRUE;
  dd.Dma64BitAddresses = FALSE;                            // V1.01
  dd.Master = TRUE;

  pdx->AdapterObject = IoGetDmaAdapter(pdx->Pdo, &dd, &pdx->nMapRegisters);
  if (!pdx->AdapterObject)
  {                                                        // If we can't create adapter object
    KdPrint((DRIVERNAME " - Unable to create DMA adapter object\n"));
    if (pdx->membase)
      MmUnmapIoSpace(pdx->membase, pdx->memsize);
    pdx->membase = NULL;
    return STATUS_UNSUCCESSFUL;
  }

  ULONG loop;
  for (loop = 0; loop < DMABUFFERS; loop++)                // Initialize our DMA buffer array
    pdx->dmaBuffers[loop].bufferId = -1;
  for (loop = 0; loop < FIFOBUFFERS; loop++)               // Initialize our FIFO buffer array
    pdx->fifoBuffers[loop].bufferId = -1;
  pdx->fifoOverflows = 0;
  for (loop = 0; loop < IRQBUFFERS; loop++)                // Initialize the IRQ buffers
  {
    pdx->irqBuffers[loop].irqsReceived = 0;
    pdx->irqBuffers[loop].irqsActiveReceived = 0;
    pdx->irqBuffers[loop].active = FALSE;
    pdx->irqBuffers[loop].eventActive = TRUE;              // By default generate notification event if someone waiting for it
    pdx->irqBuffers[loop].signalActive = FALSE;
    pdx->irqBuffers[loop].fifoActive = FALSE;
    pdx->irqBuffers[loop].irqAutoDisable = FALSE;
  }

  // Since we will be working with a common buffer we have to allocate it -> NOW DONE BY APPLICATION!
//  pdx->vaCommonBuffer =
//    (*pdx->AdapterObject->DmaOperations->AllocateCommonBuffer)
//      (pdx->AdapterObject, COMMONBUFFERSIZE, &pdx->paCommonBuffer, FALSE);
//  if (!pdx->vaCommonBuffer)
//  {                                                        // If we can't create common buffer
//    KdPrint((DRIVERNAME " - Unable to allocate common DMA buffer\n"));
//    if (pdx->membase)
//      MmUnmapIoSpace(pdx->membase, pdx->memsize);
//    pdx->membase = NULL;
//    return STATUS_UNSUCCESSFUL;
//  }
//  KdPrint((DRIVERNAME " - Common buffer @ %p, %p%\n", pdx->vaCommonBuffer, pdx->paCommonBuffer));

  // Prevent device from interrupting
  WRITE_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_IER), 0);              // Disable master IRQ
  ULONG isrstatus = READ_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_ISR));// Clear pending IRQ's
  WRITE_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_PWR), FLEXCOP_PWROFF); // Powers off

  status = IoConnectInterrupt(&pdx->InterruptObject, (PKSERVICE_ROUTINE) OnInterrupt,
                              (PVOID) pdx, NULL, vector, irql, irql, mode, irqshare, affinity, FALSE);
  if (!NT_SUCCESS(status))
  {
    KdPrint((DRIVERNAME " - IoConnectInterrupt failed - %X\n", status));
    if (pdx->membase)
      MmUnmapIoSpace(pdx->membase, pdx->memsize);
    pdx->membase = NULL;
    if (pdx->AdapterObject)
      (*pdx->AdapterObject->DmaOperations->PutDmaAdapter)
        (pdx->AdapterObject);
    pdx->AdapterObject = NULL;
    return status;
  }

  // Allow all interrupts
// DONE BY APPLICATION ->  WRITE_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_IER), 0xFFFFFFFF);     // Enable all

  return STATUS_SUCCESS;
}


// TODO If you use separate queues for reads and writes, you will need separate
// StartIo routines too.


//{ **************************************************************************** }
//{ Params   : <fdo>  Functional device object                                   }
//{            <Irp>  I/O request packet                                         }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Start I/O request packet.                                         }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
VOID StartIo(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp);
  if (!NT_SUCCESS(status))
  {
    CompleteRequest(Irp, status, 0);
    return;
  }

  PMDL mdl = Irp->MdlAddress;
  pdx->numxfer = 0;
  pdx->xfer = pdx->nbytes = MmGetMdlByteCount(mdl);
  pdx->vaddr = MmGetMdlVirtualAddress(mdl);

  ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr, pdx->nbytes);
  if (nregs > pdx->nMapRegisters)
  {                                                        // If staged transfer needed
    nregs = pdx->nMapRegisters;
    pdx->xfer = nregs * PAGE_SIZE - MmGetMdlByteOffset(mdl);
  }
  pdx->nMapRegistersAllocated = nregs;                     // Save for deallocation later

  status = (*pdx->AdapterObject->DmaOperations->AllocateAdapterChannel)
             (pdx->AdapterObject, fdo, nregs, (PDRIVER_CONTROL) AdapterControl, pdx);
  if (!NT_SUCCESS(status))
  {
    KdPrint((DRIVERNAME " - AllocateAdapterChannel failed - %X\n", status));
    StartNextPacket(&pdx->dqReadWrite, fdo);
    CompleteRequest(Irp, status, 0);
    IoReleaseRemoveLock(&pdx->RemoveLock, Irp);
  }
}


//{ **************************************************************************** }
//{ Params   : <fdo>      Functional device object                               }
//{            <oktouch>  ..                                                     }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Stop a device.                                                    }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
VOID StopDevice(IN PDEVICE_OBJECT fdo, BOOLEAN oktouch /* = FALSE */)
{
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  AbortPendingIoctls(pdx, STATUS_CANCELLED);

  if (pdx->InterruptObject)
  {                                                        // If to disconnect interrupt
    // Prevent device from generating more interrupts
    WRITE_REGISTER_ULONG((PULONG) (pdx->membase + FLEXCOP_IER), 0);  // Disable master IRQ

    IoDisconnectInterrupt(pdx->InterruptObject);
    pdx->InterruptObject = NULL;
  }

  if (pdx->membase)
    MmUnmapIoSpace(pdx->membase, pdx->memsize);
  pdx->membase = NULL;

  ULONG loop;
  // IRQ buffers
  for (loop = 0; loop < IRQBUFFERS; loop++)
  {
    pdx->irqBuffers[loop].active = FALSE;
    pdx->irqBuffers[loop].eventActive = TRUE;
    pdx->irqBuffers[loop].signalActive = FALSE;
    pdx->irqBuffers[loop].fifoActive = FALSE;
  }

  // FIFO buffers
  for (loop = 0; loop < FIFOBUFFERS; loop++)
  {
    if (pdx->fifoBuffers[loop].bufferId >= 0)              // If in use
      ExFreePool(pdx->fifoBuffers[loop].vaBuffer);         // Release the allocated memory
    pdx->fifoBuffers[loop].bufferId = -1;
  }

  // DMA buffers
  for (loop = 0; loop < DMABUFFERS; loop++)
  {
    if (pdx->dmaBuffers[loop].bufferId >= 0)               // If in use
    {                                                      // Release it
      // Release the allocated common buffer
      (*pdx->AdapterObject->DmaOperations->FreeCommonBuffer)
        (pdx->AdapterObject, pdx->dmaBuffers[loop].buffersize, pdx->dmaBuffers[loop].paBuffer,
         pdx->dmaBuffers[loop].vaBuffer, FALSE);
    }
    pdx->dmaBuffers[loop].bufferId = -1;
  }

  if (pdx->AdapterObject)
  {
    // Release common DMA buffer
//    (*pdx->AdapterObject->DmaOperations->FreeCommonBuffer)
//      (pdx->AdapterObject, COMMONBUFFERSIZE, pdx->paCommonBuffer,
//       pdx->vaCommonBuffer, FALSE);
    // Release adapter object
    (*pdx->AdapterObject->DmaOperations->PutDmaAdapter)
      (pdx->AdapterObject);
  }
  pdx->AdapterObject = NULL;
}
