libusb 0.6.0-x86-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +17 -0
  4. data/.yardopts +6 -0
  5. data/COPYING +165 -0
  6. data/Gemfile +11 -0
  7. data/History.md +124 -0
  8. data/README.md +159 -0
  9. data/Rakefile +145 -0
  10. data/appveyor.yml +23 -0
  11. data/lib/libusb.rb +58 -0
  12. data/lib/libusb/bos.rb +306 -0
  13. data/lib/libusb/call.rb +446 -0
  14. data/lib/libusb/compat.rb +376 -0
  15. data/lib/libusb/configuration.rb +155 -0
  16. data/lib/libusb/constants.rb +160 -0
  17. data/lib/libusb/context.rb +426 -0
  18. data/lib/libusb/dependencies.rb +7 -0
  19. data/lib/libusb/dev_handle.rb +564 -0
  20. data/lib/libusb/device.rb +365 -0
  21. data/lib/libusb/endpoint.rb +194 -0
  22. data/lib/libusb/eventmachine.rb +183 -0
  23. data/lib/libusb/interface.rb +60 -0
  24. data/lib/libusb/setting.rb +132 -0
  25. data/lib/libusb/ss_companion.rb +69 -0
  26. data/lib/libusb/stdio.rb +25 -0
  27. data/lib/libusb/transfer.rb +377 -0
  28. data/lib/libusb/version_gem.rb +19 -0
  29. data/lib/libusb/version_struct.rb +63 -0
  30. data/libusb.gemspec +30 -0
  31. data/test/test_libusb_bos.rb +118 -0
  32. data/test/test_libusb_bulk_stream_transfer.rb +50 -0
  33. data/test/test_libusb_capability.rb +23 -0
  34. data/test/test_libusb_compat.rb +78 -0
  35. data/test/test_libusb_compat_mass_storage.rb +81 -0
  36. data/test/test_libusb_descriptors.rb +212 -0
  37. data/test/test_libusb_event_machine.rb +118 -0
  38. data/test/test_libusb_gc.rb +37 -0
  39. data/test/test_libusb_hotplug.rb +127 -0
  40. data/test/test_libusb_iso_transfer.rb +50 -0
  41. data/test/test_libusb_mass_storage.rb +268 -0
  42. data/test/test_libusb_mass_storage2.rb +96 -0
  43. data/test/test_libusb_structs.rb +58 -0
  44. data/test/test_libusb_threads.rb +89 -0
  45. data/test/test_libusb_version.rb +40 -0
  46. data/wireshark-usb-sniffer.png +0 -0
  47. metadata +150 -0
@@ -0,0 +1,7 @@
1
+ module LIBUSB
2
+ LIBUSB_VERSION = ENV['LIBUSB_VERSION'] || '1.0.21'
3
+ LIBUSB_SOURCE_URI = "https://github.com/libusb/libusb/releases/download/v#{LIBUSB_VERSION}/libusb-#{LIBUSB_VERSION}.tar.bz2"
4
+ LIBUSB_SOURCE_SHA1 = '54d71841542eb1a6f0b0420878a4d5434efe8d28'
5
+
6
+ MINI_PORTILE_VERSION = '~> 2.1'
7
+ end
@@ -0,0 +1,564 @@
1
+ # This file is part of Libusb for Ruby.
2
+ #
3
+ # Libusb for Ruby is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Libusb for Ruby is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with Libusb for Ruby. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'libusb/call'
17
+
18
+ module LIBUSB
19
+ # Class representing a handle on a USB device.
20
+ #
21
+ # A device handle is used to perform I/O and other operations. When finished
22
+ # with a device handle, you should call DevHandle#close .
23
+ class DevHandle
24
+ # @private
25
+ attr_reader :pHandle
26
+ # @return [Device] the device this handle belongs to.
27
+ attr_reader :device
28
+
29
+ def initialize device, pHandle
30
+ @device = device
31
+ @pHandle = pHandle
32
+ @bulk_transfer = @control_transfer = @interrupt_transfer = nil
33
+ end
34
+
35
+ # Close a device handle.
36
+ #
37
+ # Should be called on all open handles before your application exits.
38
+ #
39
+ # Internally, this function destroys the reference that was added by {Device#open}
40
+ # on the given device.
41
+ #
42
+ # This is a non-blocking function; no requests are sent over the bus.
43
+ def close
44
+ @bulk_transfer.free_buffer if @bulk_transfer
45
+ @interrupt_transfer.free_buffer if @interrupt_transfer
46
+ @control_transfer.free_buffer if @control_transfer
47
+ Call.libusb_close(@pHandle)
48
+ end
49
+
50
+ def string_descriptor_ascii(index)
51
+ pString = FFI::MemoryPointer.new 0x100
52
+ res = Call.libusb_get_string_descriptor_ascii(@pHandle, index, pString, pString.size)
53
+ LIBUSB.raise_error res, "in libusb_get_string_descriptor_ascii" unless res>=0
54
+ pString.read_string(res)
55
+ end
56
+
57
+ # Claim an interface on a given device handle.
58
+ #
59
+ # You must claim the interface you wish to use before you can perform I/O on any
60
+ # of its endpoints.
61
+ #
62
+ # It is legal to attempt to claim an already-claimed interface, in which case
63
+ # libusb just returns without doing anything.
64
+ #
65
+ # Claiming of interfaces is a purely logical operation; it does not cause any
66
+ # requests to be sent over the bus. Interface claiming is used to instruct the
67
+ # underlying operating system that your application wishes to take ownership of
68
+ # the interface.
69
+ #
70
+ # This is a non-blocking function.
71
+ #
72
+ # If called with a block, the device handle is passed through to the block
73
+ # and the interface is released when the block has finished.
74
+ #
75
+ # @param [Interface, Fixnum] interface the interface or it's bInterfaceNumber you wish to claim
76
+ def claim_interface(interface)
77
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
78
+ res = Call.libusb_claim_interface(@pHandle, interface)
79
+ LIBUSB.raise_error res, "in libusb_claim_interface" if res!=0
80
+ return self unless block_given?
81
+ begin
82
+ yield self
83
+ ensure
84
+ release_interface(interface)
85
+ end
86
+ end
87
+
88
+ # Release an interface previously claimed with {DevHandle#claim_interface}.
89
+ #
90
+ # You should release all claimed interfaces before closing a device handle.
91
+ #
92
+ # This is a blocking function. A SET_INTERFACE control request will be sent to the
93
+ # device, resetting interface state to the first alternate setting.
94
+ #
95
+ # @param [Interface, Fixnum] interface the interface or it's bInterfaceNumber you
96
+ # claimed previously
97
+ def release_interface(interface)
98
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
99
+ res = Call.libusb_release_interface(@pHandle, interface)
100
+ LIBUSB.raise_error res, "in libusb_release_interface" if res!=0
101
+ end
102
+
103
+ # Set the active configuration for a device.
104
+ #
105
+ # The operating system may or may not have already set an active configuration on
106
+ # the device. It is up to your application to ensure the correct configuration is
107
+ # selected before you attempt to claim interfaces and perform other operations.
108
+ #
109
+ # If you call this function on a device already configured with the selected
110
+ # configuration, then this function will act as a lightweight device reset: it
111
+ # will issue a SET_CONFIGURATION request using the current configuration, causing
112
+ # most USB-related device state to be reset (altsetting reset to zero, endpoint
113
+ # halts cleared, toggles reset).
114
+ #
115
+ # You cannot change/reset configuration if your application has claimed interfaces -
116
+ # you should free them with {DevHandle#release_interface} first. You cannot
117
+ # change/reset configuration if other applications or drivers have claimed
118
+ # interfaces.
119
+ #
120
+ # A configuration value of +nil+ will put the device in unconfigured state. The USB
121
+ # specifications state that a configuration value of 0 does this, however buggy
122
+ # devices exist which actually have a configuration 0.
123
+ #
124
+ # You should always use this function rather than formulating your own
125
+ # SET_CONFIGURATION control request. This is because the underlying operating
126
+ # system needs to know when such changes happen.
127
+ #
128
+ # This is a blocking function.
129
+ #
130
+ # @param [Configuration, Fixnum] configuration the configuration or it's
131
+ # bConfigurationValue you wish to activate, or +nil+ if you wish to put
132
+ # the device in unconfigured state
133
+ def set_configuration(configuration)
134
+ configuration = configuration.bConfigurationValue if configuration.respond_to? :bConfigurationValue
135
+ res = Call.libusb_set_configuration(@pHandle, configuration || -1)
136
+ LIBUSB.raise_error res, "in libusb_set_configuration" if res!=0
137
+ end
138
+ alias configuration= set_configuration
139
+
140
+ # Activate an alternate setting for an interface.
141
+ #
142
+ # The interface must have been previously claimed with {DevHandle#claim_interface}.
143
+ #
144
+ # You should always use this function rather than formulating your own
145
+ # SET_INTERFACE control request. This is because the underlying operating system
146
+ # needs to know when such changes happen.
147
+ #
148
+ # This is a blocking function.
149
+ #
150
+ # @param [Setting, Fixnum] setting_or_interface_number the alternate setting
151
+ # to activate or the bInterfaceNumber of the previously-claimed interface
152
+ # @param [Fixnum, nil] alternate_setting the bAlternateSetting of the alternate setting to activate
153
+ # (only if first param is a Fixnum)
154
+ def set_interface_alt_setting(setting_or_interface_number, alternate_setting=nil)
155
+ alternate_setting ||= setting_or_interface_number.bAlternateSetting if setting_or_interface_number.respond_to? :bAlternateSetting
156
+ setting_or_interface_number = setting_or_interface_number.bInterfaceNumber if setting_or_interface_number.respond_to? :bInterfaceNumber
157
+ res = Call.libusb_set_interface_alt_setting(@pHandle, setting_or_interface_number, alternate_setting)
158
+ LIBUSB.raise_error res, "in libusb_set_interface_alt_setting" if res!=0
159
+ end
160
+
161
+ # Clear the halt/stall condition for an endpoint.
162
+ #
163
+ # Endpoints with halt status are unable to receive or transmit
164
+ # data until the halt condition is stalled.
165
+ #
166
+ # You should cancel all pending transfers before attempting to
167
+ # clear the halt condition.
168
+ #
169
+ # This is a blocking function.
170
+ #
171
+ # @param [Endpoint, Fixnum] endpoint the endpoint in question or it's bEndpointAddress
172
+ def clear_halt(endpoint)
173
+ endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
174
+ res = Call.libusb_clear_halt(@pHandle, endpoint)
175
+ LIBUSB.raise_error res, "in libusb_clear_halt" if res!=0
176
+ end
177
+
178
+ # Perform a USB port reset to reinitialize a device.
179
+ #
180
+ # The system will attempt to restore the previous configuration and
181
+ # alternate settings after the reset has completed.
182
+ #
183
+ # If the reset fails, the descriptors change, or the previous
184
+ # state cannot be restored, the device will appear to be disconnected
185
+ # and reconnected. This means that the device handle is no longer
186
+ # valid (you should close it) and rediscover the device. A Exception
187
+ # of LIBUSB::ERROR_NOT_FOUND indicates when this is the case.
188
+ #
189
+ # This is a blocking function which usually incurs a noticeable delay.
190
+ def reset_device
191
+ res = Call.libusb_reset_device(@pHandle)
192
+ LIBUSB.raise_error res, "in libusb_reset_device" if res!=0
193
+ end
194
+
195
+ if Call.respond_to?(:libusb_alloc_streams)
196
+
197
+ # @method alloc_streams
198
+ #
199
+ # Allocate up to num_streams usb bulk streams on the specified endpoints. This
200
+ # function takes an array of endpoints rather then a single endpoint because
201
+ # some protocols require that endpoints are setup with similar stream ids.
202
+ # All endpoints passed in must belong to the same interface.
203
+ #
204
+ # Note this function may return less streams then requested. Also note that the
205
+ # same number of streams are allocated for each endpoint in the endpoint array.
206
+ #
207
+ # Stream id 0 is reserved, and should not be used to communicate with devices.
208
+ # If {alloc_streams} returns with a value of N, you may use stream ids
209
+ # 1 to N.
210
+ #
211
+ # Available since libusb-1.0.19.
212
+ #
213
+ # @param [Fixnum] num_streams number of streams to try to allocate
214
+ # @param [Array<Fixnum>, Array<Endpoint>] endpoints array of endpoints to allocate streams on
215
+ # @return [Fixnum] number of streams allocated
216
+ # @see #free_streams
217
+ # @see BulkStreamTransfer
218
+ def alloc_streams(num_streams, endpoints)
219
+ pEndpoints = endpoints_as_ffi_bytes(endpoints)
220
+ res = Call.libusb_alloc_streams(@pHandle, num_streams, pEndpoints, endpoints.length)
221
+ LIBUSB.raise_error res, "in libusb_alloc_streams" unless res>=0
222
+ res
223
+ end
224
+
225
+ # @method free_streams
226
+ #
227
+ # Free usb bulk streams allocated with {alloc_streams}
228
+ #
229
+ # Note streams are automatically free-ed when releasing an interface.
230
+ #
231
+ # Available since libusb-1.0.19.
232
+ #
233
+ # @param [Array<Fixnum>, Array<Endpoint>] endpoints array of endpoints to free streams on
234
+ # @see #alloc_streams
235
+ def free_streams(endpoints)
236
+ pEndpoints = endpoints_as_ffi_bytes(endpoints)
237
+ res = Call.libusb_free_streams(@pHandle, pEndpoints, endpoints.length)
238
+ LIBUSB.raise_error res, "in libusb_free_streams" unless res>=0
239
+ nil
240
+ end
241
+
242
+ else
243
+
244
+ def alloc_streams(num_streams, endpoints)
245
+ raise NotImplementedError, "libusb-1.0.19+ is required for bulk stream transfers"
246
+ end
247
+
248
+ end
249
+
250
+ # Determine if a kernel driver is active on an interface.
251
+ #
252
+ # If a kernel driver is active, you cannot claim the interface,
253
+ # and libusb will be unable to perform I/O.
254
+ #
255
+ # @param [Interface, Fixnum] interface the interface to check or it's bInterfaceNumber
256
+ # @return [Boolean]
257
+ def kernel_driver_active?(interface)
258
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
259
+ res = Call.libusb_kernel_driver_active(@pHandle, interface)
260
+ LIBUSB.raise_error res, "in libusb_kernel_driver_active" unless res>=0
261
+ return res==1
262
+ end
263
+
264
+ # Detach a kernel driver from an interface.
265
+ #
266
+ # If successful, you will then be able to claim the interface and perform I/O.
267
+ #
268
+ # @param [Interface, Fixnum] interface the interface to detach the driver
269
+ # from or it's bInterfaceNumber
270
+ def detach_kernel_driver(interface)
271
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
272
+ res = Call.libusb_detach_kernel_driver(@pHandle, interface)
273
+ LIBUSB.raise_error res, "in libusb_detach_kernel_driver" if res!=0
274
+ end
275
+
276
+ # Re-attach an interface's kernel driver, which was previously detached
277
+ # using {DevHandle#detach_kernel_driver}.
278
+ #
279
+ # @param [Interface, Fixnum] interface the interface to attach the driver to
280
+ def attach_kernel_driver(interface)
281
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
282
+ res = Call.libusb_attach_kernel_driver(@pHandle, interface)
283
+ LIBUSB.raise_error res, "in libusb_attach_kernel_driver" if res!=0
284
+ end
285
+
286
+ # @private
287
+ if Call.respond_to?(:libusb_set_auto_detach_kernel_driver)
288
+
289
+ # @method auto_detach_kernel_driver=
290
+ # Enable/disable libusb's automatic kernel driver detachment.
291
+ #
292
+ # When this is enabled libusb will automatically detach the kernel driver on an
293
+ # interface when claiming the interface, and attach it when releasing the
294
+ # interface.
295
+ #
296
+ # Automatic kernel driver detachment is disabled on newly opened device handles by
297
+ # default.
298
+ #
299
+ # On platforms which do not have CAP_SUPPORTS_DETACH_KERNEL_DRIVER this
300
+ # function will return ERROR_NOT_SUPPORTED, and libusb will continue as if
301
+ # this function was never called.
302
+ #
303
+ # Available since libusb-1.0.16.
304
+ #
305
+ # @param [Boolean] enable whether to enable or disable auto kernel driver detachment
306
+ #
307
+ # @see LIBUSB.has_capability?
308
+ def auto_detach_kernel_driver=(enable)
309
+ res = Call.libusb_set_auto_detach_kernel_driver(@pHandle, enable ? 1 : 0)
310
+ LIBUSB.raise_error res, "in libusb_set_auto_detach_kernel_driver" if res!=0
311
+ end
312
+ end
313
+
314
+ # @private
315
+ if Call.respond_to?(:libusb_get_bos_descriptor)
316
+
317
+ # @method bos
318
+ # Get a Binary Object Store (BOS) descriptor.
319
+ #
320
+ # This is a BLOCKING function, which will send requests to the device.
321
+ #
322
+ # Since libusb version 1.0.16.
323
+ #
324
+ # @return [Bos]
325
+ def bos
326
+ ctx = device.context.instance_variable_get(:@ctx)
327
+ pp_desc = FFI::MemoryPointer.new :pointer
328
+ res = Call.libusb_get_bos_descriptor(@pHandle, pp_desc)
329
+ LIBUSB.raise_error res, "in libusb_get_bos_descriptor" if res!=0
330
+ Bos.new(ctx, pp_desc.read_pointer)
331
+ end
332
+ end
333
+
334
+ # Perform a USB bulk transfer.
335
+ #
336
+ # When called without a block, the transfer is done synchronously - so all events are handled
337
+ # internally and the sent/received data will be returned after completion or an exception will be raised.
338
+ #
339
+ # When called with a block, the method returns immediately after submitting the transfer.
340
+ # You then have to ensure, that {Context#handle_events} is called properly. As soon as the
341
+ # transfer is completed, the block is called with the sent/received data in case of success
342
+ # or the exception instance in case of failure.
343
+ #
344
+ # The direction of the transfer is inferred from the direction bits of the
345
+ # endpoint address.
346
+ #
347
+ # For bulk reads, the +:dataIn+ param indicates the maximum length of data you are
348
+ # expecting to receive. If less data arrives than expected, this function will
349
+ # return that data.
350
+ #
351
+ # You should check the returned number of bytes for bulk writes. Not all of the
352
+ # data may have been written.
353
+ #
354
+ # Also check {Error#transferred} when dealing with a timeout exception. libusb may have
355
+ # to split your transfer into a number of chunks to satisfy underlying O/S
356
+ # requirements, meaning that the timeout may expire after the first few chunks
357
+ # have completed. libusb is careful not to lose any data that may have been
358
+ # transferred; do not assume that timeout conditions indicate a complete lack of
359
+ # I/O.
360
+ #
361
+ # @param [Hash] args
362
+ # @option args [Endpoint, Fixnum] :endpoint the (address of a) valid endpoint to communicate with
363
+ # @option args [String] :dataOut the data to send with an outgoing transfer
364
+ # @option args [Fixnum] :dataIn the number of bytes expected to receive with an ingoing transfer
365
+ # @option args [Fixnum] :timeout timeout (in millseconds) that this function should wait before giving
366
+ # up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.
367
+ #
368
+ # @return [Fixnum] Number of bytes sent for an outgoing transfer
369
+ # @return [String] Received data for an ingoing transfer
370
+ # @return [self] When called with a block
371
+ #
372
+ # @yieldparam [String, Integer, LIBUSB::Error] result result of the transfer is yielded to the block,
373
+ # when the asynchronous transfer has finished
374
+ # @raise [ArgumentError, LIBUSB::Error] in case of failure
375
+ def bulk_transfer(args={}, &block)
376
+ timeout = args.delete(:timeout) || 1000
377
+ endpoint = args.delete(:endpoint) || raise(ArgumentError, "no endpoint given")
378
+ endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
379
+ if endpoint&ENDPOINT_IN != 0
380
+ dataIn = args.delete(:dataIn) || raise(ArgumentError, "no :dataIn given for bulk read")
381
+ else
382
+ dataOut = args.delete(:dataOut) || raise(ArgumentError, "no :dataOut given for bulk write")
383
+ end
384
+ raise ArgumentError, "invalid params #{args.inspect}" unless args.empty?
385
+
386
+ # reuse transfer struct to speed up transfer
387
+ @bulk_transfer ||= BulkTransfer.new dev_handle: self, allow_device_memory: true
388
+ tr = @bulk_transfer
389
+ tr.endpoint = endpoint
390
+ tr.timeout = timeout
391
+ if dataOut
392
+ tr.buffer = dataOut
393
+ else
394
+ tr.alloc_buffer(dataIn)
395
+ end
396
+
397
+ submit_transfer(tr, dataIn, 0, &block)
398
+ end
399
+
400
+ # Perform a USB interrupt transfer.
401
+ #
402
+ # When called without a block, the transfer is done synchronously - so all events are handled
403
+ # internally and the sent/received data will be returned after completion or an exception will be raised.
404
+ #
405
+ # When called with a block, the method returns immediately after submitting the transfer.
406
+ # You then have to ensure, that {Context#handle_events} is called properly. As soon as the
407
+ # transfer is completed, the block is called with the sent/received data in case of success
408
+ # or the exception instance in case of failure.
409
+ #
410
+ # The direction of the transfer is inferred from the direction bits of the
411
+ # endpoint address.
412
+ #
413
+ # For interrupt reads, the +:dataIn+ param indicates the maximum length of data you
414
+ # are expecting to receive. If less data arrives than expected, this function will
415
+ # return that data.
416
+ #
417
+ # You should check the returned number of bytes for interrupt writes. Not all of
418
+ # the data may have been written.
419
+ #
420
+ # Also check {Error#transferred} when dealing with a timeout exception. libusb may have
421
+ # to split your transfer into a number of chunks to satisfy underlying O/S
422
+ # requirements, meaning that the timeout may expire after the first few chunks
423
+ # have completed. libusb is careful not to lose any data that may have been
424
+ # transferred; do not assume that timeout conditions indicate a complete lack of
425
+ # I/O.
426
+ #
427
+ # The default endpoint bInterval value is used as the polling interval.
428
+ #
429
+ # @param [Hash] args
430
+ # @option args [Endpoint, Fixnum] :endpoint the (address of a) valid endpoint to communicate with
431
+ # @option args [String] :dataOut the data to send with an outgoing transfer
432
+ # @option args [Fixnum] :dataIn the number of bytes expected to receive with an ingoing transfer
433
+ # @option args [Fixnum] :timeout timeout (in millseconds) that this function should wait before giving
434
+ # up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.
435
+ #
436
+ # @return [Fixnum] Number of bytes sent for an outgoing transfer
437
+ # @return [String] Received data for an ingoing transfer
438
+ # @return [self] When called with a block
439
+ #
440
+ # @yieldparam [String, Integer, LIBUSB::Error] result result of the transfer is yielded to the block,
441
+ # when the asynchronous transfer has finished
442
+ # @raise [ArgumentError, LIBUSB::Error] in case of failure
443
+ def interrupt_transfer(args={}, &block)
444
+ timeout = args.delete(:timeout) || 1000
445
+ endpoint = args.delete(:endpoint) || raise(ArgumentError, "no endpoint given")
446
+ endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
447
+ if endpoint&ENDPOINT_IN != 0
448
+ dataIn = args.delete(:dataIn) || raise(ArgumentError, "no :dataIn given for interrupt read")
449
+ else
450
+ dataOut = args.delete(:dataOut) || raise(ArgumentError, "no :dataOut given for interrupt write")
451
+ end
452
+ raise ArgumentError, "invalid params #{args.inspect}" unless args.empty?
453
+
454
+ # reuse transfer struct to speed up transfer
455
+ @interrupt_transfer ||= InterruptTransfer.new dev_handle: self, allow_device_memory: true
456
+ tr = @interrupt_transfer
457
+ tr.endpoint = endpoint
458
+ tr.timeout = timeout
459
+ if dataOut
460
+ tr.buffer = dataOut
461
+ else
462
+ tr.alloc_buffer(dataIn)
463
+ end
464
+
465
+ submit_transfer(tr, dataIn, 0, &block)
466
+ end
467
+
468
+ # Perform a USB control transfer.
469
+ #
470
+ # When called without a block, the transfer is done synchronously - so all events are handled
471
+ # internally and the sent/received data will be returned after completion or an exception will be raised.
472
+ #
473
+ # When called with a block, the method returns immediately after submitting the transfer.
474
+ # You then have to ensure, that {Context#handle_events} is called properly. As soon as the
475
+ # transfer is completed, the block is called with the sent/received data in case of success
476
+ # or the exception instance in case of failure.
477
+ #
478
+ # The direction of the transfer is inferred from the +:bmRequestType+ field of the
479
+ # setup packet.
480
+ #
481
+ # @param [Hash] args
482
+ # @option args [Fixnum] :bmRequestType the request type field for the setup packet
483
+ # @option args [Fixnum] :bRequest the request field for the setup packet
484
+ # @option args [Fixnum] :wValue the value field for the setup packet
485
+ # @option args [Fixnum] :wIndex the index field for the setup packet
486
+ # @option args [String] :dataOut the data to send with an outgoing transfer, it
487
+ # is appended to the setup packet
488
+ # @option args [Fixnum] :dataIn the number of bytes expected to receive with an ingoing transfer
489
+ # (excluding setup packet)
490
+ # @option args [Fixnum] :timeout timeout (in millseconds) that this function should wait before giving
491
+ # up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.
492
+ #
493
+ # @return [Fixnum] Number of bytes sent (excluding setup packet) for outgoing transfer
494
+ # @return [String] Received data (without setup packet) for ingoing transfer
495
+ # @return [self] When called with a block
496
+ #
497
+ # @yieldparam [String, Integer, LIBUSB::Error] result result of the transfer is yielded to the block,
498
+ # when the asynchronous transfer has finished
499
+ # @raise [ArgumentError, LIBUSB::Error] in case of failure
500
+ def control_transfer(args={}, &block)
501
+ bmRequestType = args.delete(:bmRequestType) || raise(ArgumentError, "param :bmRequestType not given")
502
+ bRequest = args.delete(:bRequest) || raise(ArgumentError, "param :bRequest not given")
503
+ wValue = args.delete(:wValue) || raise(ArgumentError, "param :wValue not given")
504
+ wIndex = args.delete(:wIndex) || raise(ArgumentError, "param :wIndex not given")
505
+ timeout = args.delete(:timeout) || 1000
506
+ if bmRequestType&ENDPOINT_IN != 0
507
+ dataIn = args.delete(:dataIn) || 0
508
+ dataOut = ''
509
+ else
510
+ dataOut = args.delete(:dataOut) || ''
511
+ end
512
+ raise ArgumentError, "invalid params #{args.inspect}" unless args.empty?
513
+
514
+ # reuse transfer struct to speed up transfer
515
+ @control_transfer ||= ControlTransfer.new dev_handle: self, allow_device_memory: true
516
+ tr = @control_transfer
517
+ tr.timeout = timeout
518
+ if dataIn
519
+ setup_data = [bmRequestType, bRequest, wValue, wIndex, dataIn].pack('CCvvv')
520
+ tr.alloc_buffer( dataIn + CONTROL_SETUP_SIZE, setup_data )
521
+ else
522
+ tr.buffer = [bmRequestType, bRequest, wValue, wIndex, dataOut.bytesize, dataOut].pack('CCvvva*')
523
+ end
524
+
525
+ submit_transfer(tr, dataIn, CONTROL_SETUP_SIZE, &block)
526
+ end
527
+
528
+ private
529
+ def submit_transfer(tr, dataIn, offset)
530
+ if block_given?
531
+ tr.submit! do
532
+ res = dataIn ? tr.actual_buffer(offset) : tr.actual_length
533
+
534
+ if tr.status==:TRANSFER_COMPLETED
535
+ yield res
536
+ else
537
+ exception = Transfer::TransferStatusToError[tr.status] || ERROR_OTHER
538
+
539
+ yield exception.new("error #{tr.status}", res)
540
+ end
541
+ end
542
+ self
543
+ else
544
+ tr.submit_and_wait
545
+
546
+ res = dataIn ? tr.actual_buffer(offset) : tr.actual_length
547
+
548
+ unless tr.status==:TRANSFER_COMPLETED
549
+ raise((Transfer::TransferStatusToError[tr.status] || ERROR_OTHER).new("error #{tr.status}", res))
550
+ end
551
+ res
552
+ end
553
+ end
554
+
555
+ def endpoints_as_ffi_bytes(endpoints)
556
+ pEndpoints = FFI::MemoryPointer.new :char, endpoints.length
557
+ endpoints.each_with_index do |ep, epi|
558
+ ep = ep.bEndpointAddress if ep.respond_to? :bEndpointAddress
559
+ pEndpoints.put_uchar(epi, ep)
560
+ end
561
+ pEndpoints
562
+ end
563
+ end
564
+ end