{*******************************************************}
{                                                       }
{       nrCommLib Communication Library for Delphi      }
{       Definitions for direct Usb driver call          }
{       Copyright (c) 1997-2011 Roman Novgorodov        }
{       v9.27                                           }
{       last update: 15 Feb 2011                        }
{                                                       }
{       e-mail:noro@deepsoftware.com                    }
{       DeepSoftware                                    }
{       http://www.deepsoftware.com/                    }
{                                                       }
{*******************************************************}
{$I nrcommlib.inc}

unit nrusb_sysusb_api;
interface
uses windows, nrusbw32, nrusb_usb_api;

Type

  TnrSysUsbApi = class(TnrCustomUsbApi)
  private
    hDevice:THandle;
    pipeTimeouts: array [0 .. 255] of dword;
    pipeType: array [0 .. 255] of byte;
    function DriverCall(code:dword; ptrOut:pointer; szOut:dword; ptrIn:pointer; szIn:dword; ovl: POVERLAPPED; var szRet:dword):boolean;
    function GetLicx:dword;
    function GetIoctlCode(code:dword):dword;
  public
    function Open(nameSymbolic:string): THandle; override;
    procedure Close; override;
    function Write(idPipe:integer; data: pointer; len: integer; ovl:POVERLAPPED ):integer; override;
    function Read(idPipe:integer; data: pointer; len: integer; ovl:POVERLAPPED ):integer; override;
    function IsValid:boolean; override;
    function ResetPipe(idPipe:integer):boolean;  override;
    function AbortPipe(idPipe:integer):boolean;  override;
    function FlushPipe(idPipe:integer):boolean;  override;
    function ControlTransfer( request: TnrUsbRequest;  var LengthTransferred : dword; ovl:POVERLAPPED): boolean; override;
    function GetOverlappedResult(ovl:POVERLAPPED; var szRead:dword; timeout: dword):boolean; override;
    procedure PreparePipe(const idPipe:integer; const timeout:integer; const transferType:TnrUsbTransferType); override;
  end;


implementation

uses SysUtils, nrclasses;

Type

  TnrUsbSysRequest = packed record
    Timeout : dword;
    Licx : dword;
    case Integer of
     0: (configuration : dword);
     1: (_interface : dword;  altsetting : dword; );
     2: ( endpoint, packet_size, max_transfer_size, transfer_flags, iso_start_frame_latency : dword; );
     3: ( _type, recipient, request, value, index : dword; );
     4: (feature : packed record  recipient, feature, index : dword; end;);
     5: (status :  packed record  recipient, index, status : dword; end;);
     6: (descriptor: packed record   _type, index, language_id, recipient : dword; end;);
     7: (level : dword;);
     8: ( major, minor, micro, nano, mod_value : dword;);
		 9: (_property : dword;);
		10: (key_type, name_offset, value_offset, value_length : dword; );
  end;

Const

  NRUSB_IOCTL_BASE_INDEX  = $800; // 0x90;
//  LIBUSB_IOCTL_BASE_INDEX = $800;

  CMD_WRITE               = $0A;
  CMD_READ                = $0B;
  CMD_CONTROL_WRITE       = $0C;
  CMD_CONTROL_READ        = $0D;
  CMD_RESET_ENDPOINT      = $0E;
  CMD_ABORT_ENDPOINT      = $0F;
  CMD_ISOCHRONOUS_WRITE   = $13;
  CMD_ISOCHRONOUS_READ    = $14;


{ TnrSysUsbApi }

function TnrSysUsbApi.Open(nameSymbolic: string): THandle;
begin
  Result := CreateFile(PChar(nameSymbolic),
      GENERIC_READ + GENERIC_WRITE,
      FILE_SHARE_READ + FILE_SHARE_WRITE,
      nil,OPEN_EXISTING,FILE_FLAG_OVERLAPPED, 0);
  hDevice := Result;
end;

procedure TnrSysUsbApi.Close;
begin
  CloseHandle(hDevice);
end;

function TnrSysUsbApi.IsValid: boolean;
begin
  Result := hDevice <> INVALID_HANDLE_VALUE;
end;

function TnrSysUsbApi.GetIoctlCode(code:dword):dword;
var base_index, method_type :dword;
begin
  base_index := NRUSB_IOCTL_BASE_INDEX;
  method_type := METHOD_BUFFERED;
//  if (modeLibUsb) base_index = LIBUSB_IOCTL_BASE_INDEX;

  case code of
    CMD_WRITE,
    CMD_ISOCHRONOUS_WRITE:
        method_type := METHOD_IN_DIRECT;
    CMD_READ,
    CMD_ISOCHRONOUS_READ:
        method_type := METHOD_OUT_DIRECT;
  end;

  Result :=  ( (FILE_DEVICE_USB shl 16) or (FILE_ANY_ACCESS shl 14) or
                    ((base_index + code) shl 2) or method_type);
end;

function TnrSysUsbApi.DriverCall(code: dword; ptrOut: pointer; szOut: dword;
  ptrIn: pointer; szIn: dword; ovl: POVERLAPPED; var szRet: dword): boolean;
var o: TOVERLAPPED;
begin
  Result := False;
  if ovl = nil then begin
    ZeroMemory(@o, SizeOf(TOVERLAPPED));
    o.hEvent := CreateEvent(nil, true, false, nil);
    if o.hEvent = 0 then exit;
    ovl := @o;
  end;

  Result := DeviceIoControl(hDevice,GetIoctlCode(code), ptrOut, szOut, ptrIn, szIn, szRet, ovl);

  if not Result then begin
    if GetLastError() = ERROR_IO_PENDING then begin
      //OutputDebugString('ERROR_IO_PENDING ...');
      // if we use internal overlapped structure, we will wait result ...
      if @o = ovl
        then Result := windows.GetOverlappedResult(hDevice, ovl^, szRet, true);
      //OutputDebugString('... ERROR_IO_PENDING!!!');
    end;
  end;

  if @o = ovl then CloseHandle(o.hEvent);

end;

function TnrSysUsbApi.AbortPipe(idPipe: integer): boolean;
var req : TnrUsbSysRequest;
    sz: dword;
begin
  req.endpoint := idPipe;
  req.timeout := pipeTimeouts[idPipe];
  req.Licx := GetLicx;
  Result := DriverCall(CMD_ABORT_ENDPOINT, @req, SizeOf(req), nil, 0, nil, sz);
end;

function TnrSysUsbApi.ResetPipe(idPipe: integer): boolean;
var req : TnrUsbSysRequest;
    sz: dword;
begin
  req.endpoint := idPipe;
  req.timeout := pipeTimeouts[idPipe];
  req.Licx := GetLicx;
  Result := DriverCall(CMD_RESET_ENDPOINT, @req, SizeOf(req), nil, 0, nil, sz);
end;

function TnrSysUsbApi.FlushPipe(idPipe: integer): boolean;
begin
  Result := ResetPipe(idPipe) and AbortPipe(idPipe);
end;

procedure TnrSysUsbApi.PreparePipe(const idPipe:integer; const timeout:integer; const transferType:TnrUsbTransferType);
begin
  pipeTimeouts[idPipe] := timeout;
  pipeType[idPipe] := Byte(transferType);
end;

function TnrSysUsbApi.Read(idPipe: integer; data: pointer; len: integer;
  ovl: POVERLAPPED): integer;
var req : TnrUsbSysRequest;
    szRet, cmd: dword;
begin
  req.endpoint := idPipe;
  req.packet_size := len;
  req.timeout := pipeTimeouts[idPipe];
  req.licx := GetLicx();
  case TnrUsbTransferType(pipeType[idPipe]) of
     uttControl:     cmd := CMD_CONTROL_READ;
     uttIsochronous: cmd := CMD_ISOCHRONOUS_READ;
     else cmd := CMD_READ;
  end;
  DriverCall(cmd, @req, SizeOf(TnrUsbSysRequest), data, len, ovl, szRet);
  Result := szRet;
end;

function TnrSysUsbApi.Write(idPipe: integer; data: pointer; len: integer;
  ovl: POVERLAPPED): integer;
var req : TnrUsbSysRequest;
    szRet, cmd: dword;
begin
  req.endpoint := idPipe;
  req.packet_size := len;
  req.timeout := pipeTimeouts[idPipe];
  req.licx := GetLicx();
  case TnrUsbTransferType(pipeType[idPipe]) of
     uttControl:     cmd := CMD_CONTROL_WRITE;
     uttIsochronous: cmd := CMD_ISOCHRONOUS_WRITE;
     else cmd := CMD_WRITE;
  end;
  DriverCall(cmd, @req, SizeOf(TnrUsbSysRequest), data, len, ovl, szRet);
  Result := szRet;
end;

function TnrSysUsbApi.GetOverlappedResult(ovl: POVERLAPPED;
  var szRead: dword; timeout: dword): boolean;
var res:dword;
begin
  szRead := 0;
  res := WaitForSingleObject(ovl^.hEvent, timeout);
  if res = WAIT_OBJECT_0
    then Result := windows.GetOverlappedResult(hDevice, ovl^, szRead, true)
    else Result := false;
end;

function TnrSysUsbApi.ControlTransfer(request: TnrUsbRequest; var LengthTransferred: dword; ovl: POVERLAPPED): boolean;
var req : ^TnrUsbSysRequest;
    cmd, sz_req:dword;
begin
  LengthTransferred := 0;

  if request.ReqDirection = urdDeviceToHost then begin
    cmd :=  CMD_CONTROL_READ;
    sz_req := SizeOf(TnrUsbSysRequest);
  end else begin
    cmd :=  CMD_CONTROL_WRITE;
    sz_req := SizeOf(TnrUsbSysRequest) + request.Length;
  end;

  req := AllocMem(sz_req);

  req^._type := Dword(request.ReqType); // (requesttype >> 5) & 0x03;
//  _type, recipient, request, value, index
  req^.recipient := Dword(request.ReqRecipient); //requesttype & 0x1F;
  req^.request := request.Request;
  req^.value := request.Value;
  req^.index := request.Index;
  req^.timeout := 5000;
  req^.licx := GetLicx();

  if request.ReqDirection = urdHostToDevice
    then CopyMemory(Pointer(DWord(req) + SizeOf(TnrUsbSysRequest)), request.Data, request.Length);

  Result := DriverCall(CMD, req, sz_req, request.Data, request.Length, ovl, LengthTransferred);

  if request.ReqDirection = urdHostToDevice
    then CopyMemory(request.Data, Pointer(DWord(req) + SizeOf(TnrUsbSysRequest)), request.Length);

  FreeMem(req);

end;

function TnrSysUsbApi.GetLicx: dword;
var res: TFILETIME;
begin
  GetSystemTimeAsFileTime(res);
  Result := Dword(Pointer(Dword(@res) + 2)^);
end;

end.
