def control_transfer(args={}, &block)
      bmRequestType = args.delete(:bmRequestType) || raise(ArgumentError, "param :bmRequestType not given")
      bRequest = args.delete(:bRequest) || raise(ArgumentError, "param :bRequest not given")
      wValue = args.delete(:wValue) || raise(ArgumentError, "param :wValue not given")
      wIndex = args.delete(:wIndex) || raise(ArgumentError, "param :wIndex not given")
      timeout = args.delete(:timeout) || 1000
      if bmRequestType&ENDPOINT_IN != 0
        dataIn = args.delete(:dataIn) || 0
        dataOut = ''
      else
        dataOut = args.delete(:dataOut) || ''
      end
      raise ArgumentError, "invalid params #{args.inspect}" unless args.empty?

      # reuse transfer struct to speed up transfer
      @control_transfer ||= ControlTransfer.new dev_handle: self, allow_device_memory: true
      tr = @control_transfer
      tr.timeout = timeout
      if dataIn
        setup_data = [bmRequestType, bRequest, wValue, wIndex, dataIn].pack('CCvvv')
        tr.alloc_buffer( dataIn + CONTROL_SETUP_SIZE, setup_data )
      else
        tr.buffer = [bmRequestType, bRequest, wValue, wIndex, dataOut.bytesize, dataOut].pack('CCvvva*')
      end

      submit_transfer(tr, dataIn, CONTROL_SETUP_SIZE, &block)
    end
def control_transfer(args={}, &block)
      bmRequestType = args.delete(:bmRequestType) || raise(ArgumentError, "param :bmRequestType not given")
      bRequest = args.delete(:bRequest) || raise(ArgumentError, "param :bRequest not given")
      wValue = args.delete(:wValue) || raise(ArgumentError, "param :wValue not given")
      wIndex = args.delete(:wIndex) || raise(ArgumentError, "param :wIndex not given")
      timeout = args.delete(:timeout) || 1000
      if bmRequestType&ENDPOINT_IN != 0
        dataIn = args.delete(:dataIn) || 0
        dataOut = ''
      else
        dataOut = args.delete(:dataOut) || ''
      end
      raise ArgumentError, "invalid params #{args.inspect}" unless args.empty?

      # reuse transfer struct to speed up transfer
      @control_transfer ||= ControlTransfer.new dev_handle: self, allow_device_memory: true
      tr = @control_transfer
      tr.timeout = timeout
      if dataIn
        setup_data = [bmRequestType, bRequest, wValue, wIndex, dataIn].pack('CCvvv')
        tr.alloc_buffer( dataIn + CONTROL_SETUP_SIZE, setup_data )
      else
        tr.buffer = [bmRequestType, bRequest, wValue, wIndex, dataOut.bytesize, dataOut].pack('CCvvva*')
      end

      submit_transfer(tr, dataIn, CONTROL_SETUP_SIZE, &block)
    end
Perform a USB control transfer.

 When called without a block, the transfer is done synchronously - so all events are handled
 internally and the sent/received data will be returned after completion or an exception will be raised.

 When called with a block, the method returns immediately after submitting the transfer.
 You then have to ensure, that {Context#handle_events} is called properly. As soon as the
 transfer is completed, the block is called with the sent/received data in case of success
 or the exception instance in case of failure.

 The direction of the transfer is inferred from the +:bmRequestType+ field of the
 setup packet.

 @param [Hash] args
 @option args [Fixnum] :bmRequestType   the request type field for the setup packet
 @option args [Fixnum] :bRequest  the request field for the setup packet
 @option args [Fixnum] :wValue  the value field for the setup packet
 @option args [Fixnum] :wIndex  the index field for the setup packet
 @option args [String] :dataOut  the data to send with an outgoing transfer, it
   is appended to the setup packet
 @option args [Fixnum] :dataIn   the number of bytes expected to receive with an ingoing transfer
   (excluding setup packet)
 @option args [Fixnum] :timeout   timeout (in millseconds) that this function should wait before giving
   up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.

 @return [Fixnum] Number of bytes sent (excluding setup packet) for outgoing transfer
 @return [String] Received data (without setup packet) for ingoing transfer
 @return [self]  When called with a block

 @yieldparam [String, Integer, LIBUSB::Error] result  result of the transfer is yielded to the block,
   when the asynchronous transfer has finished
 @raise [ArgumentError, LIBUSB::Error] in case of failure