#define VERBOSE

/* Laut Buch muss dieser Zwischentreiber, der IRPs zerlegt und neue erstellt:
   Dispatch:
    IoMarkIrpPending
    IoAllocateIrp (2x)
    URBs anfordern
    Anlegen Zählvariable im ursprünglichen IRP (ungenutztes Feld Params.Key)
    IoSetCompletionRoutine (2x die gleiche, pContext = Zählvariable)
    IoCallDriver (2x)
    return STATUS_PENDING
   Komplettierung:
    IRP aufräumen, zusätzliche Ressourcen (URBs) freigeben
    ExInterlockedDecrement(pContext)
    Wenn Null: IoCompleteRequest(ursprüngliches IRP)
    return STATUS_MORE_PROCESSING_REQUIRED (entfernt IRP endgültig)
 */

#define INIT_MY_GUID	// In diese .OBJ-Datei kommt die 16-Byte-GUID hinein
#include "usb2lpt.h"

//static BOOLEAN IsW2K;

NTSTATUS DefaultPnpHandler(PDEVICE_EXTENSION X, PIRP I) {
 IoSkipCurrentIrpStackLocation(I);
 return IoCallDriver(X->ldo,I);
}

NTSTATUS OnRequestComplete(IN PDEVICE_OBJECT fdo,IN PIRP Irp,IN PKEVENT pev) {
 KeSetEvent(pev, 0, FALSE);
 return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS ForwardAndWait(PDEVICE_EXTENSION X,IN PIRP Irp) {
/* Forward request to lower level and await completion
   The processor must be at PASSIVE IRQL because this function initializes
   and waits for non-zero time on a kernel event object.
*/
 KEVENT event;
 NTSTATUS ntStatus;

 ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
  // Initialize a kernel event object to use in waiting for the lower-level
	// driver to finish processing the object. 
 KeInitializeEvent(&event, NotificationEvent, FALSE);
 IoCopyCurrentIrpStackLocationToNext(Irp);
 IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnRequestComplete,
   (PVOID) &event, TRUE, TRUE, TRUE);
 ntStatus = IoCallDriver(X->ldo, Irp);
 if (ntStatus==STATUS_PENDING) {
  KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
  ntStatus = Irp->IoStatus.Status;
 }
 return ntStatus;
}

NTSTATUS CompleteRequest(IN PIRP I,IN NTSTATUS ret,IN ULONG info) {
/* Mark I/O request complete
Arguments:
   Irp - I/O request in question
   status - returned status code
   info Additional information related to status code
Reicht <status> durch
*/
 I->IoStatus.Status=ret;
 I->IoStatus.Information=info;
 IoCompleteRequest(I,IO_NO_INCREMENT);
 return ret;
}

/* ------------- Asynchrone USBD-Aufrufe ------------- */

typedef struct{	// "Arbeitsauftrag" für kombinierte Bulk-Aus- und Eingabe
 PDEVICE_EXTENSION x;	// Unsere Geräteerweiterung
 PIRP irpj;	// Job-IRP, oder NULL für angezapften Ein/Ausgabebefehl
 PIRP irpw;	// IRP zum Bulk-Schreiben
 PIRP irpr;	// IRP zum Bulk-Lesen (NULL für Nur-Schreiben)
 NTSTATUS stat;	// zurückzugebender Status (initialisieren mit 0)
 ULONG rlen;	// Gelesene Bytes (initialisieren mit 0)
 struct _URB_BULK_OR_INTERRUPT_TRANSFER urbw;	// Ohne extra ExAllocatePool
 struct _URB_BULK_OR_INTERRUPT_TRANSFER urbr;	// entfällt bei irpr=0
}JOB,*PJOB;

void SetUsbStatus(PDEVICE_EXTENSION X, NTSTATUS *stat, PURB U) {
 if (!USBD_SUCCESS(U->UrbHeader.Status)) {
  Vlpt_KdPrint(("*** USB-Fehlerkode %X\n",U->UrbHeader.Status));
  X->LastFailedUrbStatus=U->UrbHeader.Status;
  if (stat && NT_SUCCESS(*stat)) *stat=STATUS_UNSUCCESSFUL;
 }
}

// Komplettierungsroutine für IOCTL_OutIn und PortTrap
NTSTATUS OutInFertig(PDEVICE_OBJECT fdo,PIRP I,PJOB J) {
// fdo enthält keinen brauchbaren Zeiger!
 if (NT_SUCCESS(J->stat)) J->stat=I->IoStatus.Status;
 if (I==J->irpw) {
  SetUsbStatus(J->x,&J->stat,(PURB)&J->urbw);
  Vlpt_KdPrint2(("Ausgabe von %d Bytes FERTIG\n",J->urbw.TransferBufferLength));
  J->irpw=NULL;			// erledigt!
 }else if (I==J->irpr) {
  SetUsbStatus(J->x,&J->stat,(PURB)&J->urbr);
  J->rlen=J->urbr.TransferBufferLength;
  Vlpt_KdPrint2(("Eingabe von %d Bytes FERTIG\n",J->rlen));
  J->irpr=NULL;			// erledigt!
 }
 IoFreeIrp(I);
 if (!J->irpw && !J->irpr) {	// Alles erledigt?
  if (J->irpj) {
   CompleteRequest(J->irpj,J->stat,J->rlen);
  }else{
   J->x->bfill=0;
   KeSetEvent(&J->x->ev,0,FALSE);	// bfill-Ampel auf Grün
  }
  ExFreePool(J);		// Job (Arbeitsauftrag) samt URBs erledigt
 }
 return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS AsyncCallUSBD(PDEVICE_EXTENSION X,PJOB J,PIRP I,PURB U) {
 PIO_STACK_LOCATION nextStack;
 Vlpt_KdPrint2(("Aufruf AsyncCallUSBD\n"));
 ASSERT(I);
 nextStack=IoGetNextIrpStackLocation(I);		//IRQL<=2
 ASSERT(nextStack);
 if (!nextStack) return STATUS_UNSUCCESSFUL;
 nextStack->MajorFunction=IRP_MJ_INTERNAL_DEVICE_CONTROL;
 nextStack->Parameters.DeviceIoControl.IoControlCode
   =IOCTL_INTERNAL_USB_SUBMIT_URB;
 nextStack->Parameters.Others.Argument1=U;
 IoSetCompletionRoutine(I,OutInFertig,J,TRUE,TRUE,TRUE);//IRQL<=2
 return IoCallDriver(X->ldo,I);				//IRQL<=2
}

#define Alloc(len) ExAllocatePoolWithTag(NonPagedPool,len,'tplV')


NTSTATUS OutInCheck(PDEVICE_EXTENSION X, ULONG ol, ULONG il) {
// Prüfen der Parameter für Aus- und Eingabe, nur für IOCTL
 PUSBD_INTERFACE_INFORMATION ii=X->Interface;
 int i;
 if (!ii) {
  Vlpt_KdPrint(("OutInCheck(): keine Interface-Info!\n"));
  return STATUS_UNSUCCESSFUL;
 }
 if (ii->NumberOfPipes<2) {
  Vlpt_KdPrint(("OutInCheck(): Zu wenig Pipes!\n"));
  return STATUS_UNSUCCESSFUL;
 }
 for (i=0; i<2; i++) {
  PUSBD_PIPE_INFORMATION p=ii->Pipes+i;
  if (!(p->PipeType==UsbdPipeTypeBulk)) {
   Vlpt_KdPrint(("OutInCheck(): Pipe nicht vom Typ BULK!\n"));
   return STATUS_UNSUCCESSFUL;
  }
  if (!p->PipeHandle) {
   Vlpt_KdPrint(("OutInCheck(): kein Pipe-Handle!\n"));
   return STATUS_UNSUCCESSFUL;
  }
 }
 if (ol>ii->Pipes[0].MaximumTransferSize) {
  Vlpt_KdPrint(("OutInCheck(): Zu große Ausgabe-Transferlänge!\n"));
  return STATUS_INVALID_PARAMETER;
 }
 if (il>ii->Pipes[1].MaximumTransferSize) {
  Vlpt_KdPrint(("OutInCheck(): Zu große Eingabe-Transferlänge!\n"));
  return STATUS_INVALID_PARAMETER;
 }
 return STATUS_SUCCESS;
}

NTSTATUS OutIn(PDEVICE_EXTENSION X, PBYTE ob, ULONG ol, PBYTE ib, ULONG il,
  PULONG bytesread, PIRP I) {
// Aus- und Eingabe über die beiden Pipes zum/vom USB2LPT
// bytesread ("gelesene Bytes") = NULL -> kurzer Transfer nicht OK
 PUSBD_INTERFACE_INFORMATION ii=X->Interface;
 NTSTATUS ret=STATUS_SUCCESS;
 PJOB J;
#define USIZE sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER)

 Vlpt_KdPrint2(("Aufruf OutIn (ol=%d, il=%d)\n",ol,il));
 if (!ol && !il) return ret;
 J=Alloc(il?sizeof(JOB):sizeof(JOB)-USIZE);
 if (!J) return STATUS_NO_MEMORY;
 RtlZeroMemory(J,sizeof(JOB)-USIZE-USIZE);
 J->x=X;
 J->irpj=I;
 if (!I) KeClearEvent(&X->ev);	// Ampel auf ROT
 if (ol) {
// IoBuildDeviceIoControlRequest geht nicht wegen PASSIVE_LEVEL!
  UsbBuildInterruptOrBulkTransferRequest((PURB)&J->urbw,
    USIZE,			//size of urb
    ii->Pipes[0].PipeHandle,	// Erste Pipe = Schreiben
    ob,NULL,ol,0,NULL);
  J->irpw=IoAllocateIrp(X->ldo->StackSize,FALSE);
  if (!J->irpw) ret=STATUS_INSUFFICIENT_RESOURCES;
 }
 if (il && NT_SUCCESS(ret)) {
  UsbBuildInterruptOrBulkTransferRequest((PURB)&J->urbr,
    USIZE,			//size of urb
    ii->Pipes[1].PipeHandle,	// Zweite Pipe = Lesen
    ib,NULL,il,
    bytesread			// je nachdem!
     ? USBD_TRANSFER_DIRECTION_IN|USBD_SHORT_TRANSFER_OK
     : USBD_TRANSFER_DIRECTION_IN,
    NULL);
  J->irpr=IoAllocateIrp(X->ldo->StackSize,FALSE);
  if (!J->irpr) ret=STATUS_INSUFFICIENT_RESOURCES;
 }
 if (ol && NT_SUCCESS(ret)) ret=AsyncCallUSBD(X,J,J->irpw,(PURB)&J->urbw);
 if (!NT_SUCCESS(ret) && J->irpw) {IoFreeIrp(J->irpw); J->irpw=NULL;}
 if (il && NT_SUCCESS(ret)) ret=AsyncCallUSBD(X,J,J->irpr,(PURB)&J->urbr);
 if (!NT_SUCCESS(ret) && J->irpr) {IoFreeIrp(J->irpr); J->irpr=NULL;}
// Problem, wenn das erste AsyncCallUSBD klappte und das zweite schief geht!
 if (!NT_SUCCESS(ret)) ExFreePool(J);
#undef USIZE
 return ret;		// Jetzt sind bis zu zwei URBs in Arbeit
}

BYTE ab(PDEVICE_EXTENSION X, WORD adr) {	// Adress-Byte ermitteln
 adr-=X->uc.LptBase;	// sollte 0..7 oder 400h..403h liefern
 if (adr&0x400) adr|=8;	// ECP-Adressbyte draus machen
 return (BYTE)adr;
}

void _stdcall HandleOut(PDEVICE_EXTENSION X,WORD adr,BYTE b) {
 BYTE a;	// Adress-Byte für USB
 ASSERT(KeGetCurrentIrql()==0);
 Vlpt_KdPrint2(("HandleOut(%03Xh,%02Xh)\n",adr,b));
 if (KeGetCurrentIrql()) return;		// kann nicht behandeln!
 if (!NT_SUCCESS(IoAcquireRemoveLock(&X->rlock,NULL))) return;
 if (KeWaitForMutexObject(&X->bmutex,Executive,KernelMode,FALSE,NULL)
   !=STATUS_SUCCESS) goto ex2;
 KeWaitForSingleObject(&X->ev,Suspended,KernelMode,FALSE,NULL);	// Timer abwarten
 ASSERT(X->bfill<=62 && !(X->bfill&1));	// gerade!
 if (X->bfill==62) {	// Puffer gerammelt voll: ausgeben!
//  ASSERT(!(X->f&trapping));
//  X->f|=trapping;
  if (X->uc.flags&UC_WriteCache) KeCancelTimer(&X->wrcache.tmr);
  if (!NT_SUCCESS(OutIn(X,X->buffer,X->bfill,NULL,0,NULL,NULL))) goto ex;
  Vlpt_KdPrint2(("Warten auf Ende von OutIn()\n"));
  KeWaitForSingleObject(&X->ev,Suspended,KernelMode,FALSE,NULL);
  Vlpt_KdPrint2(("Das Warten hat ein Ende!\n"));
//  X->f&=~trapping;
 }
 a=ab(X,adr);
 switch (a) {
  case 0: {
   X->mirror[0]|=UC_ReadCache0;
   X->mirror[1]=b;
  }break;
  case 2: {
   X->mirror[0]|=UC_ReadCache2;
   X->mirror[2]=b;
  }break;
 }
 X->buffer[X->bfill++]=a;
 X->buffer[X->bfill++]=b;
 if (X->uc.flags&UC_WriteCache) {
  KeSetTimer(&X->wrcache.tmr,
    RtlConvertLongToLargeInteger(X->uc.TimeOut*-10000),
    &X->wrcache.dpc);	// Zeit wird nicht größer als DWORD
 }else{				// sofort versenden!
//  ASSERT(!(X->f&trapping));
//  X->f|=trapping;
  if (!NT_SUCCESS(OutIn(X,X->buffer,X->bfill,NULL,0,NULL,NULL))) goto ex;
  KeWaitForSingleObject(&X->ev,Suspended,KernelMode,FALSE,NULL);
//  X->f&=~trapping;
 }
ex:
 KeReleaseMutex(&X->bmutex,FALSE);
ex2:
 IoReleaseRemoveLock(&X->rlock,NULL);
 Vlpt_KdPrint2(("HandleOut EXIT\n"));
}

BYTE _stdcall HandleIn(PDEVICE_EXTENSION X,WORD adr) {
 BYTE a,b=0xFF;
 
 ASSERT(KeGetCurrentIrql()==0);
 Vlpt_KdPrint2(("HandleIn ENTER\n"));
 if (KeGetCurrentIrql()) goto ex3;		// kann nicht behandeln!
 if (!NT_SUCCESS(IoAcquireRemoveLock(&X->rlock,NULL))) goto ex3;
 if (KeWaitForMutexObject(&X->bmutex,Executive,KernelMode,FALSE,NULL)
   !=STATUS_SUCCESS) goto ex2;
 KeWaitForSingleObject(&X->ev,Suspended,KernelMode,FALSE,NULL);	// Timer abwarten
 ASSERT(X->bfill<=62 && !(X->bfill&1));	// gerade!

 a=ab(X,adr);
 switch (a) {
  case 0: if (X->mirror[0]&X->uc.flags&UC_ReadCache0) {
   b=X->mirror[1];
   goto ex;
  }break;
  case 2: if (X->mirror[0]&X->uc.flags&UC_ReadCache2) {
   b=X->mirror[2]|0xE0;
   goto ex;
  }break;
 }
 X->buffer[X->bfill++]=a|0x10;

// ASSERT(!(X->f&trapping));
// X->f|=trapping;
 if (X->uc.flags&UC_WriteCache) KeCancelTimer(&X->wrcache.tmr);
 if (!NT_SUCCESS(OutIn(X,X->buffer,X->bfill,&b,1,NULL,NULL))) goto ex;
 Vlpt_KdPrint2(("Warten auf Ende von OutIn()\n"));
 KeWaitForSingleObject(&X->ev,Suspended,KernelMode,FALSE,NULL);
 Vlpt_KdPrint2(("Das Warten hat ein Ende!\n"));
// X->f&=~trapping;
ex:
 KeReleaseMutex(&X->bmutex,FALSE);
ex2:
 IoReleaseRemoveLock(&X->rlock,NULL);
ex3:
 Vlpt_KdPrint2(("HandleIn(%03Xh) liefert %02Xh\n",adr,b));
 return b;
}

VOID TimerDpc(IN PKDPC Dpc,PDEVICE_EXTENSION X,PVOID a,PVOID b) {
 ASSERT(KeGetCurrentIrql()==DISPATCH_LEVEL);
 Vlpt_KdPrint2(("TimerDpc, Ausgabe %u Bytes\n",X->bfill));
// X->f|=trapping;
 OutIn(X,X->buffer,X->bfill,NULL,0,NULL,NULL);
// X->f&=~trapping;
}

/* ------------- Synchrone USBD-Aufrufe ------------- */

NTSTATUS CallUSBD(PDEVICE_EXTENSION X, PURB U) {
/* Passes a Usb Request Block (URB) to the USB class driver (USBD)
   Diese Routine blockiert!! IRQL=0!!
   Ein sinnvolles TimeOut wäre hier dringend angeraten!!??
*/
 NTSTATUS ret;
 PIRP I;
 PIO_STACK_LOCATION nextStack;
 KEVENT ev;
 IO_STATUS_BLOCK ios;

 Vlpt_KdPrint2(("Aufruf CallUSBD\n"));
 ASSERT(!KeGetCurrentIrql());
 KeInitializeEvent(&ev,NotificationEvent,FALSE);
 I=IoBuildDeviceIoControlRequest(			//IRQL=0
   IOCTL_INTERNAL_USB_SUBMIT_URB,X->ldo,NULL,0,NULL,0,TRUE,&ev,&ios);
 ASSERT(I);
 if (!I) return STATUS_INSUFFICIENT_RESOURCES;
 nextStack=IoGetNextIrpStackLocation(I);
 ASSERT(nextStack);
 nextStack->Parameters.Others.Argument1=U;
 ret=IoCallDriver(X->ldo,I);
 Vlpt_KdPrint2(("IoCallDriver(USBD) returns %x\n",ret));
 if (ret==STATUS_PENDING) {
  KeWaitForSingleObject(&ev,Suspended,KernelMode,FALSE,NULL);
  ret=ios.Status;					//IRQL=0 sonst Crash!
 }
#if DBG
 if (U->UrbHeader.Status || ret)	// im Fehlerfall zucken!
   Vlpt_KdPrint(("URB status %X, IRP status %X\n",U->UrbHeader.Status,ret));
#endif
 if (!USBD_SUCCESS(U->UrbHeader.Status)) {
  X->LastFailedUrbStatus=U->UrbHeader.Status;
  if (NT_SUCCESS(ret)) ret=STATUS_UNSUCCESSFUL;
 }
 return ret;
}

NTSTATUS SelectInterfaces(PDEVICE_EXTENSION X,
//XREF: ConfigureDevice
  IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor,
  IN PUSBD_INTERFACE_INFORMATION Interface) {
/*
    Initializes an Vlpt Device with multiple interfaces
Arguments:
    fdo            -  this instance of the Vlpt Device
    ConfigurationDescriptor
		- the USB configuration descriptor containing the interface
		  and endpoint descriptors.
    Interface	- pointer to a USBD Interface Information Object
		- If this is NULL, then this driver must choose its interface
		  based on driver-specific criteria, and the driver must also
		  CONFIGURE the device.
		- If it is NOT NULL, then the driver has already been given
		  an interface and the device has already been configured by
		  the parent of this device driver.
Return Value:
    NT status code
*/
 NTSTATUS ntStatus;
 PURB urb;
 ULONG j;
// UCHAR alternateSetting /*, MyInterfaceNumber*/;
 PUSBD_INTERFACE_INFORMATION interfaceObject;
 USBD_INTERFACE_LIST_ENTRY interfaceList[2];

 Vlpt_KdPrint2(("enter SelectInterfaces\n"));

// MyInterfaceNumber = SAMPLE_INTERFACE_NBR;

	// Search the configuration descriptor for the first interface/alternate setting

 interfaceList[0].InterfaceDescriptor =
 USBD_ParseConfigurationDescriptorEx(ConfigurationDescriptor,
                                       ConfigurationDescriptor,
                                       -1,         // Interface - don't care
                                       -1,         // Alternate Setting - don't care
                                       -1,         // Class - don't care
                                       -1,         // SubClass - don't care
                                       -1);        // Protocol - don't care

 ASSERT(interfaceList[0].InterfaceDescriptor);

 interfaceList[1].InterfaceDescriptor=NULL;
 interfaceList[1].Interface=NULL;

 urb=USBD_CreateConfigurationRequestEx(ConfigurationDescriptor,&interfaceList[0]);

 if (!urb) Vlpt_KdPrint((" USBD_CreateConfigurationRequestEx failed\n"));
// DumpBuffer(urb, urb->UrbHeader.Length);

 interfaceObject=(PUSBD_INTERFACE_INFORMATION) (&(urb->UrbSelectConfiguration.Interface));
   // We set up a default max transfer size for the endpoints.  Your driver will
   // need to change this to reflect the capabilities of your device's endpoints.
 for (j=0; j<interfaceList[0].InterfaceDescriptor->bNumEndpoints; j++)
   interfaceObject->Pipes[j].MaximumTransferSize = (64 * 1024) - 1;


 ntStatus=CallUSBD(X, urb);
// DumpBuffer(urb, urb->UrbHeader.Length);

 if (NT_SUCCESS(ntStatus) && USBD_SUCCESS(urb->UrbHeader.Status)) {
      // Save the configuration handle for this device
//  X->ConfigurationHandle=urb->UrbSelectConfiguration.ConfigurationHandle;
  X->Interface=Alloc(interfaceObject->Length);
      // save a copy of the interfaceObject information returned
  RtlCopyMemory(X->Interface,interfaceObject,interfaceObject->Length);
      // Dump the interfaceObject to the debugger
  Vlpt_KdPrint (("---------\n"));
  Vlpt_KdPrint (("NumberOfPipes    %d\n",  X->Interface->NumberOfPipes));
  Vlpt_KdPrint (("Length           %d\n",  X->Interface->Length));
  Vlpt_KdPrint (("Alt Setting      0x%x\n",X->Interface->AlternateSetting));
  Vlpt_KdPrint (("Interface Number 0x%x\n",X->Interface->InterfaceNumber));

      // Dump the pipe info
  for (j=0; j<interfaceObject->NumberOfPipes; j++) {
   PUSBD_PIPE_INFORMATION i;
   i=&X->Interface->Pipes[j];
   Vlpt_KdPrint (("---------\n"));
   Vlpt_KdPrint (("PipeType        0x%x\n",i->PipeType));
   Vlpt_KdPrint (("EndpointAddress 0x%x\n",i->EndpointAddress));
   Vlpt_KdPrint (("MaxPacketSize   %d\n",  i->MaximumPacketSize));
   Vlpt_KdPrint (("Interval        %d\n",  i->Interval));
   Vlpt_KdPrint (("Handle          0x%x\n",i->PipeHandle));
   Vlpt_KdPrint (("MaxTransferSize %d\n",  i->MaximumTransferSize));
  }
  Vlpt_KdPrint (("---------\n"));
 }

 Vlpt_KdPrint2(("leave SelectInterfaces (%x)\n",ntStatus));
 return ntStatus;
}


NTSTATUS ConfigureDevice(PDEVICE_EXTENSION X) {
//XREF: StartDevice
/*
Routine Description:
   Configures the USB device via USB-specific device requests and interaction
   with the USB software subsystem.

Arguments:
   fdo - pointer to the device object for this instance of the Vlpt Device

Return Value:
   NT status code
*/
 NTSTATUS ntStatus=STATUS_NO_MEMORY;
// PURB urb=NULL;
 struct _URB_CONTROL_DESCRIPTOR_REQUEST urb;
 PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor=NULL;
 ULONG siz;

 Vlpt_KdPrint2(("enter ConfigureDevice\n"));

   // Get memory for the USB Request Block (urb).
// if (!Alloc(&urb,sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST))) goto NoMemory;
      //
      // Set size of the data buffer.  Note we add padding to cover hardware faults
      // that may cause the device to go past the end of the data buffer
      //
 siz=sizeof(USB_CONFIGURATION_DESCRIPTOR)+16;
        // Get the nonpaged pool memory for the data buffer
 if (!(configurationDescriptor=Alloc(siz))) goto NoMemory;
 UsbBuildGetDescriptorRequest((PURB)&urb,
   (USHORT)sizeof(urb),
   USB_CONFIGURATION_DESCRIPTOR_TYPE,0,0,configurationDescriptor,
   NULL,sizeof(USB_CONFIGURATION_DESCRIPTOR),/* Get only the configuration descriptor */
   NULL);

 ntStatus=CallUSBD(X,(PURB)&urb);

 if (NT_SUCCESS(ntStatus)) {
  Vlpt_KdPrint2(("Configuration Descriptor is at %x, bytes txferred: %d\n"
    "Configuration Descriptor Actual Length: %d\n",
    configurationDescriptor,
    urb./*UrbControlDescriptorRequest.*/TransferBufferLength,
    configurationDescriptor->wTotalLength));
 }//if
 siz=configurationDescriptor->wTotalLength+16;	// Größe merken
 ExFreePool(configurationDescriptor);		// bisherigen Deskriptor verwerfen
 ntStatus=STATUS_NO_MEMORY;
 if (!(configurationDescriptor=Alloc(siz))) goto NoMemory;	// neu allozieren
 UsbBuildGetDescriptorRequest((PURB)&urb,
   (USHORT)sizeof(urb),
   USB_CONFIGURATION_DESCRIPTOR_TYPE,0,0,configurationDescriptor,
   NULL,siz,  /* Get all the descriptor data*/ NULL);
 ntStatus=CallUSBD(X,(PURB)&urb);

 if (NT_SUCCESS(ntStatus)) {
  Vlpt_KdPrint2(("Entire Configuration Descriptor is at %x, bytes txferred: %d\n",
    configurationDescriptor,
    urb./*UrbControlDescriptorRequest.*/TransferBufferLength));
// We have the configuration descriptor for the configuration
// we want.
// Now we issue the SelectConfiguration command to get
// the  pipes associated with this configuration.
  ntStatus=SelectInterfaces(X,configurationDescriptor,NULL); // NULL=Device not yet configured
 }
NoMemory:
 ExFreePool(configurationDescriptor);
// Free(&urb);
 Vlpt_KdPrint2(("leave ConfigureDevice (%x)\n", ntStatus));
 return ntStatus;
}

void FreeTraps(PDEVICE_EXTENSION X) {
 int i;
//  TRAP();
 if (CurThreadPtr && X->instance<3) {	// Patch vorerst auf 9x beschränken
  *(PUSHORT)(0x408+X->instance*2)=X->oldlpt;
  *(PUSHORT)(0x410)=X->oldsys;
 }
 for (i=0; i<3; i++) if (X->debugreg[i]) {
  FreeDRN(X->debugreg[i]-1);
  X->debugreg[i]=0;
 }
 X->mirror[0]=0;		// Spiegel löschen
}

void SetTraps(PDEVICE_EXTENSION X) {
 int e;
 X->f= X->f&~No_Function | (X->uc.flags&UC_Function ? 0 : No_Function);
 if (CurThreadPtr && X->instance<3) {	// Patch vorerst auf 9x beschränken
  register PUSHORT a1=(PUSHORT)(0x408+X->instance*2);
  X->oldlpt=*a1;
  X->oldsys=*(PUSHORT)(0x410);
  *a1=X->uc.LptBase;
  if (X->oldsys>>14 <= X->instance) {
   *(PUSHORT)(0x410)=X->oldsys&0x3FFF | ((X->instance+1)<<14);
  }
 }
 e=AllocDR(X->uc.LptBase,X);	// Debugregister anfordern
 if (e>=0) X->debugreg[0]=e+1;	// abspeichern
 else Vlpt_KdPrint(("Kann LptBase (SPP) nicht anzapfen (%d)\n",e));
 if (X->uc.Mode&1) {		// EPP
  e=AllocDR((WORD)(X->uc.LptBase+4),X);
  if (e>=0) X->debugreg[1]=e+1;
  else Vlpt_KdPrint(("Kann LptBase+4 (EPP) nicht anzapfen (%d)\n",e));
 }
 if (X->uc.Mode&2) {		// ECP
  e=AllocDR((WORD)(X->uc.LptBase+0x400),X);
  if (e>=0) X->debugreg[2]=e+1;
  else Vlpt_KdPrint(("Kann LptBase+400h (ECP) nicht anzapfen (%d)\n",e));
 }
}

static const WORD Ports[]={0x378,0x278,0x3BC};

NTSTATUS StartDevice(PDEVICE_EXTENSION X) {
//XREF: HandleStartDevice
/* Initializes a given instance of the Vlpt Device on the USB.
   fdo - pointer to the device object for this instance of a Vlpt Device*/
 NTSTATUS ntStatus;
 PUSB_DEVICE_DESCRIPTOR DDesc;
// PURB urb;
 struct _URB_CONTROL_DESCRIPTOR_REQUEST U;
// const ULONG siz=sizeof(USB_DEVICE_DESCRIPTOR);

 Vlpt_KdPrint (("enter StartDevice\n"));

    // Get some memory from then non paged pool (fixed, locked system memory)
    // for use by the USB Request Block (urb) for the specific USB Request we
    // will be performing below (a USB device request).
// if (Alloc(&urb,sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST))) {
        // Get some non paged memory for the device descriptor contents
  if ((DDesc=Alloc(sizeof(*DDesc)))) {
            // Use a macro in the standard USB header files to build the URB
   UsbBuildGetDescriptorRequest((PURB)&U,sizeof(U),
     USB_DEVICE_DESCRIPTOR_TYPE,0,0,DDesc,NULL,sizeof(*DDesc),NULL);
            // Get the device descriptor
   ntStatus=CallUSBD(X,(PURB)&U);
            // Dump out the descriptor info to the debugger
   if (NT_SUCCESS(ntStatus)) {
    Vlpt_KdPrint((
      "Device Descriptor = %x, len %x\n",
      "Vlpt Device Descriptor:\n"
      "-------------------------\n"
      "bLength\t%d\n"
      "bDescriptorType\t0x%x\n"
      "bcdUSB\t%#x\n"
      "bDeviceClass\t%#x\n"
      "bDeviceSubClass\t%#x\n"
      "bDeviceProtocol\t%#x\n"
      "bMaxPacketSize0\t%#x\n"
      "idVendor\t%#x\n"
      "idProduct\t%#x\n"
      "bcdDevice\t%#x\n"
      "iManufacturer\t%#x\n"
      "iProduct\t%#x\n"
      "iSerialNumber\t%#x\n"
      "bNumConfigurations\t%#x\n",
      DDesc,U.TransferBufferLength,
      DDesc->bLength,
      DDesc->bDescriptorType,
      DDesc->bcdUSB,
      DDesc->bDeviceClass,
      DDesc->bDeviceSubClass,
      DDesc->bDeviceProtocol,
      DDesc->bMaxPacketSize0,
      DDesc->idVendor,
      DDesc->idProduct,
      DDesc->bcdDevice,
      DDesc->iManufacturer,
      DDesc->iProduct,
      DDesc->iSerialNumber,
      DDesc->bNumConfigurations));
   }
  }else ntStatus = STATUS_NO_MEMORY;
  if (NT_SUCCESS(ntStatus)) {
            // Put a ptr to the device descriptor in the device extension for easy
            // access (like a "cached" copy).  We will free this memory when the
            // device is removed.  See the "RemoveDevice" code.
   X->DeviceDescriptor = DDesc;
   X->f&=~Stopped;
  }else if (DDesc) {
            /*
            // If the bus transaction failed, then free up the memory created to hold
            // the device descriptor, since the device is probably non-functional
            */
   ExFreePool(DDesc);
   X->DeviceDescriptor=NULL;
  }
//  ExFreePool(urb);
// }else ntStatus = STATUS_NO_MEMORY;
    // If the Get_Descriptor call was successful, then configure the device.
 if (NT_SUCCESS(ntStatus)) ntStatus = ConfigureDevice(X);
 if (NT_SUCCESS(ntStatus)) {
  PUSBD_INTERFACE_INFORMATION	ii;
  PUSBD_PIPE_INFORMATION	pi;
  ii=X->Interface;
  if (!ii) goto ex;
  if (ii->NumberOfPipes<2) goto ex;
  pi=ii->Pipes;		// Erste Pipe = Schreiben (s.a. Firmware)
  if (pi->PipeType!=UsbdPipeTypeBulk /*=2*/) goto ex;
  if (pi->MaximumTransferSize<64) goto ex;
  if (!(pi->PipeHandle)) goto ex;
  pi=ii->Pipes+1;		// Zweite Pipe = Lesen
  if (pi->PipeType!=UsbdPipeTypeBulk /*=2*/) goto ex;
  if (pi->MaximumTransferSize<64) goto ex;
  if (!(pi->PipeHandle)) goto ex;
  KeInitializeMutex(&X->bmutex,0);
  KeInitializeTimer(&X->wrcache.tmr);
  KeInitializeDpc(&X->wrcache.dpc,TimerDpc,X);
  KeInitializeEvent(&X->ev,NotificationEvent,TRUE);
//IoInitializeDpcRequest(X->fdo,...)
  TRAP();
  if (X->uc.LptBase&0xFF00) SetTraps(X);
ex:;
 }
 Vlpt_KdPrint (("leave StartDevice (%x)\n", ntStatus));
 return ntStatus;
}

NTSTATUS Quatsch(PUNICODE_STRING us,PCSZ str) {
// Unicode-String aus ASCIIZ initialisieren
 ANSI_STRING as;
 RtlInitAnsiString(&as,str);
 return RtlAnsiStringToUnicodeString(us,&as,TRUE);
}

NTSTATUS HandleStartDevice(PDEVICE_EXTENSION X, PIRP I) {
//XREF: DispatchPnp
 NTSTATUS ntStatus;
 HANDLE key;
 ntStatus=ForwardAndWait(X,I);	// Zuerst niedere Treiber starten lassen
 if (!NT_SUCCESS(IoSetDeviceInterfaceState(&X->ifname,TRUE))) {
  Vlpt_KdPrint (("IoSetDeviceInterfaceState versagt!\n"));
  TRAP();
 }
 if (X->instance<elemof(Ports)) {
  X->uc.LptBase=Ports[X->instance];	// Vorgaben setzen
  X->uc.TimeOut=200;
  X->uc.flags=UC_Function|UC_Debugreg|UC_WriteCache;
 }
 if (!NT_SUCCESS(IoOpenDeviceRegistryKey(X->pdo,PLUGPLAY_REGKEY_DEVICE,
   KEY_QUERY_VALUE,&key))) {
  Vlpt_KdPrint (("IoOpenDeviceRegistryKey versagt!\n"));
  TRAP();
 }else{
  UNICODE_STRING us;
  struct {
   KEY_VALUE_PARTIAL_INFORMATION v;
   UCHAR data[sizeof(TUserCfg)-1];	// insgesamt 6 Bytes Daten
  }val;
  ULONG needed;
  static const CHAR Tag[]="UserCfg";
  TRAP();
  if (NT_SUCCESS(Quatsch(&us,Tag))) {
   if (NT_SUCCESS(ZwQueryValueKey(key,&us,KeyValuePartialInformation,
     &val.v,sizeof(val),&needed))
   && val.v.Type==REG_BINARY
   && needed>=sizeof(val)) {
    X->uc=*(PUserCfg)val.v.Data;		// Persistente Daten kopieren
   }
   RtlFreeUnicodeString(&us);
  }
  ZwClose(key);
 }
 if (!NT_SUCCESS(ntStatus)) return CompleteRequest(I,ntStatus,I->IoStatus.Information);
  // now do whatever we need to do to start the device
 return CompleteRequest(I,StartDevice(X),0);
}

NTSTATUS StopDevice(PDEVICE_EXTENSION X) {
//XREF: DispatchPnp
 NTSTATUS ret=STATUS_SUCCESS;
 PURB U;

 Vlpt_KdPrint2(("enter StopDevice\n"));
 KeCancelTimer(&X->wrcache.tmr);
 FreeTraps(X);
#define USIZE sizeof(struct _URB_SELECT_CONFIGURATION)
 U=Alloc(USIZE);
 if (U) {
  NTSTATUS status;
  UsbBuildSelectConfigurationRequest(U,USIZE,NULL);	// Auf "unkonfiguriert"
  status=CallUSBD(X,U);
  Vlpt_KdPrint(("Device Configuration Closed status = %x usb status = %x.\n",
    status, U->UrbHeader.Status));
  ExFreePool(U);
#undef USIZE
 }else ret=STATUS_NO_MEMORY;
 Vlpt_KdPrint2(("leave StopDevice (%x)\n",ret));
 return ret;
}

NTSTATUS AbortPipe(PDEVICE_EXTENSION X,IN USBD_PIPE_HANDLE PipeHandle) {
//XREF: HandleRemoveDevice, ProcessIOCTL
/*   cancel pending transfers for a pipe */
 NTSTATUS ntStatus;
 PURB U;

 Vlpt_KdPrint2(("VLPT.SYS: AbortPipe \n"));
#define USIZE sizeof(struct _URB_PIPE_REQUEST)
 U=Alloc(USIZE);
 if (U) {
  RtlZeroMemory(U,USIZE);
  U->UrbHeader.Length=(USHORT)USIZE;
  U->UrbHeader.Function=URB_FUNCTION_ABORT_PIPE;
  U->UrbPipeRequest.PipeHandle=PipeHandle;
#ifdef WIN95
  {
   USBD_VERSION_INFORMATION VersionInformation;
      // kludge.  Win98 changed the size of the URB_PIPE_REQUEST.
      // Check the USBDI version.  If it is prior to win98 (0x101)
      // make the structure smaller.
   USBD_GetUSBDIVersion(&VersionInformation);
   if (VersionInformation.USBDI_Version<0x101) {
    Vlpt_KdPrint(("AbortPipe() Detected OSR2.1\n"));
    U->UrbHeader.Length-=sizeof(ULONG);
   }
  }
#endif
  ntStatus=CallUSBD(X,U);
  ExFreePool(U);
#undef USIZE
 }else ntStatus=STATUS_INSUFFICIENT_RESOURCES;	// Wieso diesmal Resources??
 return ntStatus;
}


NTSTATUS DevName(PUNICODE_STRING us,int Instance) {
// liefert "durchnummerierten" Device-String
 static const CHAR deviceName[]="\\Device\\Parallel?";
 NTSTATUS ret;
 ret=Quatsch(us,deviceName);
 if (NT_SUCCESS(ret)) us->Buffer[sizeof(deviceName)-2]=(USHORT)('0'+Instance);
 return ret;
}

NTSTATUS DevLink(PUNICODE_STRING us,int Instance) {
// liefert "durchnummerierten" DosDevices-String
 static const CHAR deviceLink[]="\\DosDevices\\LPT?";
 NTSTATUS ret;
 ret=Quatsch(us,deviceLink);
 if (NT_SUCCESS(ret)) us->Buffer[sizeof(deviceLink)-2]=(USHORT)('1'+Instance);
 return ret;
}

NTSTATUS RemoveDevice(PDEVICE_EXTENSION X) {
//XREF: HandleRemoveDevice
 UNICODE_STRING ucDeviceLink;

 Vlpt_KdPrint(("RemoveDevice\n"));
 if (X->DeviceDescriptor) ExFreePool(X->DeviceDescriptor);
   // Free up any interface structures in our device extension
 if (X->Interface) ExFreePool(X->Interface);
   // remove the device object's symbolic link
 if (NT_SUCCESS(DevLink(&ucDeviceLink,X->instance))) {
  IoDeleteSymbolicLink(&ucDeviceLink);
  RtlFreeUnicodeString(&ucDeviceLink);
 }
 RtlFreeUnicodeString(&X->ifname);
 IoDetachDevice(X->ldo);
 IoDeleteDevice(X->fdo);
 return STATUS_SUCCESS;
}


NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION X, PIRP I) {
//XREF: DispatchPnp
 PUSBD_INTERFACE_INFORMATION ii=X->Interface;
 ULONG i;
   // set the removing flag to prevent any new I/O's
 X->f|=removing;
 IoSetDeviceInterfaceState(&X->ifname,FALSE);
   // brute force - send an abort pipe message to all pipes to cancel any
   // pending transfers.  this should solve the problem of the driver blocking
   // on a REMOVE message because there is a pending transfer.
 if (ii) for (i=0; i<ii->NumberOfPipes; i++) {
  AbortPipe(X,(USBD_PIPE_HANDLE)ii->Pipes[i].PipeHandle);
 }
// UnlockDevice(X);		// once for LockDevice at start of dispatch
// UnlockDevice(X);		// once for initialization during AddDevice
 IoReleaseRemoveLockAndWait(&X->rlock,NULL);
// KeWaitForSingleObject(&X->evRemove,Executive,KernelMode,FALSE,NULL);
 RemoveDevice(X);
 return DefaultPnpHandler(X,I);	// lower-level completed IoStatus already
}

#if DBG
const char* LocateStr(const char*l,int n) {
// Findet in Multi-SZ-String <l> den String mit Index <n>
// und liefert Zeiger darauf. Ist n zu groß, Zeiger auf letzte Null
 for(;;n--){
  if (!n || !*l) return l;	// gefunden oder Liste zu Ende
  while (*++l);			// kein strlen zu finden!!
  l++;				// Hinter die Null
 }
}
#endif

NTSTATUS DispatchPnp(IN PDEVICE_OBJECT fdo,IN PIRP I) {
 PIO_STACK_LOCATION irpStack;
 PDEVICE_EXTENSION X=fdo->DeviceExtension;
 ULONG fcn;
 NTSTATUS ret;
#if DBG
 static const char MsgNames[]=
   "START_DEVICE\0"
   "QUERY_REMOVE_DEVICE\0"
   "REMOVE_DEVICE\0"
   "CANCEL_REMOVE_DEVICE\0"
   "STOP_DEVICE\0"
   "QUERY_STOP_DEVICE\0"
   "CANCEL_STOP_DEVICE\0"
   "QUERY_DEVICE_RELATIONS\0"
   "QUERY_INTERFACE\0"
   "QUERY_CAPABILITIES\0"
   "QUERY_RESOURCES\0"
   "QUERY_RESOURCE_REQUIREMENTS\0"
   "QUERY_DEVICE_TEXT\0"
   "FILTER_RESOURCE_REQUIREMENTS\0"
   "?\0"
   "READ_CONFIG\0"
   "WRITE_CONFIG\0"
   "EJECT\0"
   "SET_LOCK\0"
   "QUERY_ID\0"
   "QUERY_PNP_DEVICE_STATE\0"
   "QUERY_BUS_INFORMATION\0"
   "DEVICE_USAGE_NOTIFICATION\0"
   "SURPRISE_REMOVAL\0";
#endif

 ret=IoAcquireRemoveLock(&X->rlock,NULL);
 if (!NT_SUCCESS(ret)) return CompleteRequest(I,ret,0);
   // Get a pointer to the current location in the Irp. This is where
   //     the function codes and parameters are located.
 irpStack=IoGetCurrentIrpStackLocation(I);

 ASSERT(irpStack->MajorFunction==IRP_MJ_PNP);
 fcn=irpStack->MinorFunction;
 Vlpt_KdPrint(("IRP_MJ_PNP:IRP_MN_%s\n",LocateStr(MsgNames,fcn)));
// TRAP();
 switch (fcn) {
  case IRP_MN_START_DEVICE: {
   ret=HandleStartDevice(X,I);
   if (NT_SUCCESS(ret)) X->f|=Started;
  }break;

  case IRP_MN_STOP_DEVICE: {// first pass the request down the stack
   DefaultPnpHandler(X,I);
   ret=StopDevice(X);
  }break;

  case IRP_MN_SURPRISE_REMOVAL: {
   FreeTraps(X);
   X->f|=surprise;
  }goto def;

  case IRP_MN_REMOVE_DEVICE: {
   FreeTraps(X);
  }return HandleRemoveDevice(X,I);	// ohne IoReleaseRemoveLock()

  case IRP_MN_QUERY_CAPABILITIES: {
	// This code swiped from Walter Oney.  Please buy his book!!
   PDEVICE_CAPABILITIES pdc=irpStack->Parameters.DeviceCapabilities.Capabilities;
   if (pdc->Version<1) goto def;
   ret=ForwardAndWait(X,I);
   if (NT_SUCCESS(ret)) {				// IRP succeeded
    pdc=irpStack->Parameters.DeviceCapabilities.Capabilities;
	// setting this field prevents NT5 from notifying the user
	// when the device is removed.
    pdc->SurpriseRemovalOK = TRUE;
   }						// IRP succeeded
   ret=CompleteRequest(I,ret,I->IoStatus.Information);
  }break;
def:
  default: ret=DefaultPnpHandler(X,I);
 }
 IoReleaseRemoveLock(&X->rlock,NULL);
 return ret;
}


NTSTATUS DispatchPower(IN PDEVICE_OBJECT fdo,IN PIRP I) {
 PDEVICE_EXTENSION X=fdo->DeviceExtension;
 NTSTATUS ntStatus;
 Vlpt_KdPrint (("Vlpt_DispatchPower\n"));
 TRAP();
 I->IoStatus.Status=STATUS_SUCCESS;
 I->IoStatus.Information=0;
#if 1
// IoSkipCurrentIrpStackLocation(I);	// Geht nicht! Warum?
 IoCopyCurrentIrpStackLocationToNext(I);
#else
 {
  PIO_STACK_LOCATION irpStack,nextStack;
  irpStack=IoGetCurrentIrpStackLocation(I);
  nextStack=IoGetNextIrpStackLocation(I);
  ASSERT(nextStack);
  RtlCopyMemory(nextStack,irpStack,sizeof(IO_STACK_LOCATION));
 }
#endif
#ifdef WIN95
 ntStatus=IoCallDriver(X->ldo,I);
#else
 PoStartNextPowerIrp(I);
 ntStatus=PoCallDriver(X->ldo,I);
#endif
 if (ntStatus==STATUS_PENDING) IoMarkIrpPending(I);
 return ntStatus;
}


NTSTATUS CreateDeviceObject(IN PDRIVER_OBJECT DriverObject,
  OUT PDEVICE_OBJECT *fdo,LONG Instance) {
/*  Creates a Functional DeviceObject
Arguments:
    Instance - Geräte-Nummer, null-basiert
Return Value:
    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise
*/

 NTSTATUS ntStatus;
 UNICODE_STRING ucDeviceName;
 UNICODE_STRING ucDeviceLink;

 DevName(&ucDeviceName,Instance);
 Vlpt_KdPrint(("Gerätename: <%ws>\n", ucDeviceName.Buffer));
 ntStatus=IoCreateDevice(DriverObject,sizeof(DEVICE_EXTENSION),
   &ucDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,fdo);

 if (NT_SUCCESS(ntStatus)) {
  if (NT_SUCCESS(DevLink(&ucDeviceLink,Instance))) {
   Vlpt_KdPrint(("DOS-Gerätename: <%ws>\n",ucDeviceLink.Buffer));
   IoCreateSymbolicLink(&ucDeviceLink,&ucDeviceName);
   RtlFreeUnicodeString(&ucDeviceLink);
  }
 }
 RtlFreeUnicodeString(&ucDeviceName);
 return ntStatus;
}

#define MAX_VLPT_DEVICES 9

NTSTATUS AddDevice(IN PDRIVER_OBJECT drv,IN PDEVICE_OBJECT pdo) {
/*  This routine is called to create a new instance of the device
Arguments:
    DriverObject - driver object for this instance of Vlpt
    pdo - a device object created by the bus
*/
 NTSTATUS		ntStatus = STATUS_SUCCESS,j;
 PDEVICE_OBJECT		fdo;
 PDEVICE_EXTENSION	X;
 int instance;
// char e;

 Vlpt_KdPrint2(("enter AddDevice\n"));
 instance=-1;
 do{
  ++instance;
  if (CurThreadPtr && instance<3
  && *(PUSHORT)(0x408+instance*2)) ntStatus=-1;	// nur 9x
  else ntStatus=CreateDeviceObject(drv,&fdo,instance);
 }while (!NT_SUCCESS(ntStatus) && (instance<MAX_VLPT_DEVICES-1));

 if (NT_SUCCESS(ntStatus)) {
  fdo->Flags|=DO_DIRECT_IO|DO_POWER_PAGABLE;
  X=fdo->DeviceExtension;
  RtlZeroMemory(X,sizeof(DEVICE_EXTENSION));
  X->instance=instance;
  X->fdo=fdo;	// Rück-Bezug setzen
  X->pdo=pdo;	// Physikalisches Gerät (Bustreiber) setzen
  j=IoRegisterDeviceInterface(pdo,&Vlpt_GUID,NULL,&X->ifname);
  if (NT_SUCCESS(j)) {
   Vlpt_KdPrint(("Interface-Name: <%ws>, Ergebnis %d\n",X->ifname.Buffer,j));
  }
  IoInitializeRemoveLock(&X->rlock,0,1,100);
  X->ldo=IoAttachDeviceToDeviceStack(fdo,pdo);	// niederes Gerät ankoppeln
  ASSERT(X->ldo);
  fdo->Flags&=~DO_DEVICE_INITIALIZING;
 }
 Vlpt_KdPrint2(("leave AddDevice (%x)\n", ntStatus));
 return ntStatus;
}

// number of bytes of firmware to download per setup transfer
#define CHUNK_SIZE 64

NTSTATUS AnchorDownload(PDEVICE_EXTENSION X,
  WORD offset,PBYTE downloadBuffer,ULONG downloadSize) {
/*
   Uses the ANCHOR LOAD vendor specific command to download code to the EZ-USB
   device.  The actual code is stored as data within the driver binary in the
   global 'firmware' which is an VLPT_FIRMWARE struct included in the file
   firmware.c.
Arguments:
   fdo - pointer to the device object for this instance of an Vlpt Device
   offset=8051-XRAM-Speicherzieladresse
   downloadBuffer - pointer to the firmware image
   downloadSize - total size (bytes) of the firmware image to download
Return Value:
   STATUS_SUCCESS if successful,
   STATUS_UNSUCCESSFUL otherwise
*/
 NTSTATUS ret;
 PURB U;

 if (!downloadSize) return STATUS_SUCCESS;
#define USIZE sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST)
 U=Alloc(USIZE);
 if (!U) return STATUS_NO_MEMORY;
 do{
  ULONG len=downloadSize; if (len>CHUNK_SIZE) len=CHUNK_SIZE;
  RtlZeroMemory(U,USIZE);
  U->UrbHeader.Length=USIZE;
  U->UrbHeader.Function=URB_FUNCTION_VENDOR_DEVICE;
  U->UrbControlVendorClassRequest.TransferBufferLength=len;
  U->UrbControlVendorClassRequest.TransferBuffer=downloadBuffer;
//  U->UrbControlVendorClassRequest.TransferBufferMDL=NULL;
  U->UrbControlVendorClassRequest.Request=0xA0;
  U->UrbControlVendorClassRequest.Value=offset;
//  U->UrbControlVendorClassRequest.Index=0;
  ret=CallUSBD(X,U);
  if (!NT_SUCCESS(ret)) break;
  downloadBuffer+=len;
  offset+=(WORD)len;
  downloadSize-=len;
 }while (downloadSize);
 ExFreePool(U);
#undef USIZE
 return ret;
}

NTSTATUS ProcessIOCTL(IN PDEVICE_OBJECT dev,IN PIRP I) {
 PIO_STACK_LOCATION irpStack;
 PVOID iob;
 ULONG il;	// Eingabedatenlänge (OUT-Befehle und IN-Adressen)
 ULONG ol;	// Ausgabedatenlänge (IN-Daten)
 PDEVICE_EXTENSION X=dev->DeviceExtension;
 NTSTATUS ret;
 ULONG rlen=0;	// im Standardfall keine Bytes zurück

 Vlpt_KdPrint2(("IRP_MJ_DEVICE_CONTROL\n"));
 if (X->f&removing) {
  ret=STATUS_DEVICE_REMOVED;
  goto exi;
 }
 ret=IoAcquireRemoveLock(&X->rlock,NULL);
 if (!NT_SUCCESS(ret)) goto exi;

 irpStack=IoGetCurrentIrpStackLocation(I);
 iob=I->AssociatedIrp.SystemBuffer;
 il=irpStack->Parameters.DeviceIoControl.InputBufferLength;
 ol=irpStack->Parameters.DeviceIoControl.OutputBufferLength;
 ret=STATUS_SUCCESS;

 switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {
  case IOCTL_VLPT_UserCfg: {
   Vlpt_KdPrint(("IOCTL_VLPT_UserCfg\n"));
   if (il==sizeof(TUserCfg)) {
    FreeTraps(X);
    X->uc=*(PUserCfg)iob;	// Konfiguration setzen
    SetTraps(X);
   }
   if (ol==sizeof(TUserCfg)) {
    *(PUserCfg)iob=X->uc;	// Konfiguration lesen
    rlen=sizeof(TUserCfg);
   }
  }break;
  case IOCTL_VLPT_AccessCnt: {
   Vlpt_KdPrint(("IOCTL_VLPT_AccessCnt\n"));
   if (il==sizeof(TAccessCnt)) {
    KIRQL o;
    KeRaiseIrql(DISPATCH_LEVEL,&o);	// Kontextwechsel verhindern
    X->ac=*(PAccessCnt)iob;	// Zugriffszähler (null)setzen
    KeLowerIrql(o);
   }
   if (ol==sizeof(TAccessCnt)) {
    *(PAccessCnt)iob=X->ac;	// Zugriffszähler lesen
    rlen=sizeof(TAccessCnt);
   }
  }break;
  case IOCTL_VLPT_OutIn: {	// Bedienung der beiden Pipes
   Vlpt_KdPrint(("IOCTL_VLPT_OutIn\n"));
   ret=OutInCheck(X,il,ol);	// il und ol sind absichtlich vertauscht!
   if (!NT_SUCCESS(ret)) break;
   IoMarkIrpPending(I);		// Eigentlich per Mutex Sequenz schützen!?
   ret=OutIn(X,iob,il,iob,ol,&rlen,I);
  }break;
  case IOCTL_VLPT_AbortPipe: {	// Eine Pipe abbrechen 
   Vlpt_KdPrint(("IOCTL_VLPT_AbortPipe\n"));
   if (ol>=sizeof(ULONG)) {
    AbortPipe(X,X->Interface->Pipes[*(PULONG)iob].PipeHandle);
   }else ret=STATUS_INVALID_PARAMETER;
  }break;
  case IOCTL_VLPT_GetLastError: {	// Letzter USB-Fehler
   Vlpt_KdPrint(("IOCTL_VLPT_GetLastError\n"));
   if (ol>=sizeof(ULONG)) {	// Nur wenn Puffer groß genug ist!
    *((PULONG)iob)=X->LastFailedUrbStatus;
    rlen=sizeof(ULONG);
   }else ret=STATUS_INVALID_PARAMETER;
  }break;
  case IOCTL_VLPT_AnchorDownload: {
   Vlpt_KdPrint(("IOCTL_VLPT_AnchorDownload\n"));
   if (il==sizeof(WORD) && ol) {
    ret=AnchorDownload(X,*(PWORD)iob,
      MmGetSystemAddressForMdl(I->MdlAddress),ol);
    break;
   }else ret=STATUS_INVALID_PARAMETER;
  }break;
  default: ret=STATUS_INVALID_PARAMETER;
 }
 IoReleaseRemoveLock(&X->rlock,NULL);
exi:
 if (ret==STATUS_PENDING) return ret;
 return CompleteRequest(I,ret,rlen);
}

NTSTATUS Create(IN PDEVICE_OBJECT dev,IN PIRP I) {
// Aufruf von CreateFile() (könnte "\\.\Vlpt-x\yyzz" sein).
 PDEVICE_EXTENSION X=(PDEVICE_EXTENSION)dev->DeviceExtension;

 Vlpt_KdPrint2(("Enter Create()\n"));
 if (!X->f&Started) {
  Vlpt_KdPrint2(("Öffnen ohne zu starten!\n"));
  TRAP();
  return CompleteRequest(I,STATUS_UNSUCCESSFUL,0);
 }
// X->OpenHandles++;
 return CompleteRequest(I,STATUS_SUCCESS,0);
}


NTSTATUS Close(IN PDEVICE_OBJECT dev,IN PIRP I) {
// Aufrif von CloseHandle()
 PDEVICE_EXTENSION X=(PDEVICE_EXTENSION)dev->DeviceExtension;

 Vlpt_KdPrint2(("Close()\n"));
// X->OpenHandles--;
 return CompleteRequest(I,STATUS_SUCCESS,0);
}

VOID Unload(IN PDRIVER_OBJECT DriverObject) {
// Wenn das letzte Gerät verschwindet, verschwindet auch der Treiber
 Vlpt_KdPrint2(("Unload()\n"));
 TRAP();
 UnhookSyscalls();
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
  IN PUNICODE_STRING RegistryPath) {
/* Installable driver initialization entry point.
   This is where the driver is called when the driver is being loaded
   by the I/O system.  This entry point is called directly by the I/O system.
Arguments:
   DriverObject - driver object
   RegistryPath - unicode string representing the path
                  to driver-specific key in the registry
*/
 Vlpt_KdPrint (("DriverEntry (Build: "__DATE__ "/" __TIME__"\n"));
 TRAP();
 HookSyscalls();
 PrepareDR();
// IsW2K=IoIsWdmVersionAvailable(1,10);
   // Create dispatch points for the various events handled by this
   // driver.  For example, device I/O control calls (e.g., when a Win32
   // application calls the DeviceIoControl function) will be dispatched to
   // routine specified below in the IRP_MJ_DEVICE_CONTROL case.
 DriverObject->MajorFunction[IRP_MJ_CREATE] = Create;
 DriverObject->MajorFunction[IRP_MJ_CLOSE] = Close;
 DriverObject->DriverUnload = Unload;
 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ProcessIOCTL;
 DriverObject->MajorFunction[IRP_MJ_PNP]  = DispatchPnp;
 DriverObject->MajorFunction[IRP_MJ_POWER]= DispatchPower;
 DriverObject->DriverExtension->AddDevice = AddDevice;
 return STATUS_SUCCESS;
}

