libusb 0.7.0-x64-mingw-ucrt
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.appveyor.yml +33 -0
- data/.github/workflows/ci.yml +185 -0
- data/.gitignore +9 -0
- data/.travis.yml +26 -0
- data/.yardopts +6 -0
- data/COPYING +165 -0
- data/Gemfile +19 -0
- data/History.md +193 -0
- data/README.md +184 -0
- data/Rakefile +79 -0
- data/lib/libusb/bos.rb +362 -0
- data/lib/libusb/call.rb +622 -0
- data/lib/libusb/compat.rb +376 -0
- data/lib/libusb/configuration.rb +154 -0
- data/lib/libusb/constants.rb +170 -0
- data/lib/libusb/context.rb +576 -0
- data/lib/libusb/context_reference.rb +38 -0
- data/lib/libusb/dependencies.rb +7 -0
- data/lib/libusb/dev_handle.rb +574 -0
- data/lib/libusb/device.rb +407 -0
- data/lib/libusb/endpoint.rb +195 -0
- data/lib/libusb/eventmachine.rb +187 -0
- data/lib/libusb/gem_helper.rb +151 -0
- data/lib/libusb/interface.rb +60 -0
- data/lib/libusb/libusb_recipe.rb +29 -0
- data/lib/libusb/setting.rb +132 -0
- data/lib/libusb/ss_companion.rb +72 -0
- data/lib/libusb/stdio.rb +25 -0
- data/lib/libusb/transfer.rb +418 -0
- data/lib/libusb/version_gem.rb +19 -0
- data/lib/libusb/version_struct.rb +63 -0
- data/lib/libusb-1.0.dll +0 -0
- data/lib/libusb.rb +146 -0
- data/libusb.gemspec +28 -0
- data/test/test_libusb.rb +42 -0
- data/test/test_libusb_bos.rb +140 -0
- data/test/test_libusb_bulk_stream_transfer.rb +61 -0
- data/test/test_libusb_compat.rb +78 -0
- data/test/test_libusb_compat_mass_storage.rb +81 -0
- data/test/test_libusb_context.rb +88 -0
- data/test/test_libusb_descriptors.rb +245 -0
- data/test/test_libusb_event_machine.rb +118 -0
- data/test/test_libusb_gc.rb +52 -0
- data/test/test_libusb_hotplug.rb +129 -0
- data/test/test_libusb_iso_transfer.rb +56 -0
- data/test/test_libusb_mass_storage.rb +268 -0
- data/test/test_libusb_mass_storage2.rb +96 -0
- data/test/test_libusb_structs.rb +87 -0
- data/test/test_libusb_threads.rb +89 -0
- data/wireshark-usb-sniffer.png +0 -0
- metadata +112 -0
@@ -0,0 +1,574 @@
|
|
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(timeout: 1000,
|
376
|
+
endpoint:,
|
377
|
+
dataIn: nil,
|
378
|
+
dataOut: nil,
|
379
|
+
allow_device_memory: false,
|
380
|
+
&block)
|
381
|
+
|
382
|
+
endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
|
383
|
+
if endpoint&ENDPOINT_IN != 0
|
384
|
+
dataIn || raise(ArgumentError, "no :dataIn given for bulk read")
|
385
|
+
else
|
386
|
+
dataOut || raise(ArgumentError, "no :dataOut given for bulk write")
|
387
|
+
end
|
388
|
+
|
389
|
+
# reuse transfer struct to speed up transfer
|
390
|
+
@bulk_transfer ||= BulkTransfer.new dev_handle: self, allow_device_memory: allow_device_memory
|
391
|
+
tr = @bulk_transfer
|
392
|
+
tr.endpoint = endpoint
|
393
|
+
tr.timeout = timeout
|
394
|
+
if dataOut
|
395
|
+
tr.buffer = dataOut
|
396
|
+
else
|
397
|
+
tr.alloc_buffer(dataIn)
|
398
|
+
end
|
399
|
+
|
400
|
+
submit_transfer(tr, dataIn, 0, &block)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Perform a USB interrupt transfer.
|
404
|
+
#
|
405
|
+
# When called without a block, the transfer is done synchronously - so all events are handled
|
406
|
+
# internally and the sent/received data will be returned after completion or an exception will be raised.
|
407
|
+
#
|
408
|
+
# When called with a block, the method returns immediately after submitting the transfer.
|
409
|
+
# You then have to ensure, that {Context#handle_events} is called properly. As soon as the
|
410
|
+
# transfer is completed, the block is called with the sent/received data in case of success
|
411
|
+
# or the exception instance in case of failure.
|
412
|
+
#
|
413
|
+
# The direction of the transfer is inferred from the direction bits of the
|
414
|
+
# endpoint address.
|
415
|
+
#
|
416
|
+
# For interrupt reads, the +:dataIn+ param indicates the maximum length of data you
|
417
|
+
# are expecting to receive. If less data arrives than expected, this function will
|
418
|
+
# return that data.
|
419
|
+
#
|
420
|
+
# You should check the returned number of bytes for interrupt writes. Not all of
|
421
|
+
# the data may have been written.
|
422
|
+
#
|
423
|
+
# Also check {Error#transferred} when dealing with a timeout exception. libusb may have
|
424
|
+
# to split your transfer into a number of chunks to satisfy underlying O/S
|
425
|
+
# requirements, meaning that the timeout may expire after the first few chunks
|
426
|
+
# have completed. libusb is careful not to lose any data that may have been
|
427
|
+
# transferred; do not assume that timeout conditions indicate a complete lack of
|
428
|
+
# I/O.
|
429
|
+
#
|
430
|
+
# The default endpoint bInterval value is used as the polling interval.
|
431
|
+
#
|
432
|
+
# @param [Hash] args
|
433
|
+
# @option args [Endpoint, Fixnum] :endpoint the (address of a) valid endpoint to communicate with
|
434
|
+
# @option args [String] :dataOut the data to send with an outgoing transfer
|
435
|
+
# @option args [Fixnum] :dataIn the number of bytes expected to receive with an ingoing transfer
|
436
|
+
# @option args [Fixnum] :timeout timeout (in millseconds) that this function should wait before giving
|
437
|
+
# up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.
|
438
|
+
#
|
439
|
+
# @return [Fixnum] Number of bytes sent for an outgoing transfer
|
440
|
+
# @return [String] Received data for an ingoing transfer
|
441
|
+
# @return [self] When called with a block
|
442
|
+
#
|
443
|
+
# @yieldparam [String, Integer, LIBUSB::Error] result result of the transfer is yielded to the block,
|
444
|
+
# when the asynchronous transfer has finished
|
445
|
+
# @raise [ArgumentError, LIBUSB::Error] in case of failure
|
446
|
+
def interrupt_transfer(timeout: 1000,
|
447
|
+
endpoint:,
|
448
|
+
dataIn: nil,
|
449
|
+
dataOut: nil,
|
450
|
+
allow_device_memory: false,
|
451
|
+
&block)
|
452
|
+
endpoint = endpoint.bEndpointAddress if endpoint.respond_to? :bEndpointAddress
|
453
|
+
if endpoint&ENDPOINT_IN != 0
|
454
|
+
dataIn || raise(ArgumentError, "no :dataIn given for interrupt read")
|
455
|
+
else
|
456
|
+
dataOut || raise(ArgumentError, "no :dataOut given for interrupt write")
|
457
|
+
end
|
458
|
+
|
459
|
+
# reuse transfer struct to speed up transfer
|
460
|
+
@interrupt_transfer ||= InterruptTransfer.new dev_handle: self, allow_device_memory: allow_device_memory
|
461
|
+
tr = @interrupt_transfer
|
462
|
+
tr.endpoint = endpoint
|
463
|
+
tr.timeout = timeout
|
464
|
+
if dataOut
|
465
|
+
tr.buffer = dataOut
|
466
|
+
else
|
467
|
+
tr.alloc_buffer(dataIn)
|
468
|
+
end
|
469
|
+
|
470
|
+
submit_transfer(tr, dataIn, 0, &block)
|
471
|
+
end
|
472
|
+
|
473
|
+
# Perform a USB control transfer.
|
474
|
+
#
|
475
|
+
# When called without a block, the transfer is done synchronously - so all events are handled
|
476
|
+
# internally and the sent/received data will be returned after completion or an exception will be raised.
|
477
|
+
#
|
478
|
+
# When called with a block, the method returns immediately after submitting the transfer.
|
479
|
+
# You then have to ensure, that {Context#handle_events} is called properly. As soon as the
|
480
|
+
# transfer is completed, the block is called with the sent/received data in case of success
|
481
|
+
# or the exception instance in case of failure.
|
482
|
+
#
|
483
|
+
# The direction of the transfer is inferred from the +:bmRequestType+ field of the
|
484
|
+
# setup packet.
|
485
|
+
#
|
486
|
+
# @param [Hash] args
|
487
|
+
# @option args [Fixnum] :bmRequestType the request type field for the setup packet
|
488
|
+
# @option args [Fixnum] :bRequest the request field for the setup packet
|
489
|
+
# @option args [Fixnum] :wValue the value field for the setup packet
|
490
|
+
# @option args [Fixnum] :wIndex the index field for the setup packet
|
491
|
+
# @option args [String] :dataOut the data to send with an outgoing transfer, it
|
492
|
+
# is appended to the setup packet
|
493
|
+
# @option args [Fixnum] :dataIn the number of bytes expected to receive with an ingoing transfer
|
494
|
+
# (excluding setup packet)
|
495
|
+
# @option args [Fixnum] :timeout timeout (in millseconds) that this function should wait before giving
|
496
|
+
# up due to no response being received. For an unlimited timeout, use value 0. Defaults to 1000 ms.
|
497
|
+
#
|
498
|
+
# @return [Fixnum] Number of bytes sent (excluding setup packet) for outgoing transfer
|
499
|
+
# @return [String] Received data (without setup packet) for ingoing transfer
|
500
|
+
# @return [self] When called with a block
|
501
|
+
#
|
502
|
+
# @yieldparam [String, Integer, LIBUSB::Error] result result of the transfer is yielded to the block,
|
503
|
+
# when the asynchronous transfer has finished
|
504
|
+
# @raise [ArgumentError, LIBUSB::Error] in case of failure
|
505
|
+
def control_transfer(bmRequestType:,
|
506
|
+
bRequest:,
|
507
|
+
wValue:,
|
508
|
+
wIndex:,
|
509
|
+
timeout: 1000,
|
510
|
+
dataIn: nil,
|
511
|
+
dataOut: nil,
|
512
|
+
allow_device_memory: false,
|
513
|
+
&block)
|
514
|
+
|
515
|
+
if bmRequestType&ENDPOINT_IN != 0
|
516
|
+
raise ArgumentError, "invalid param :dataOut" unless dataOut.nil?
|
517
|
+
dataIn ||= 0
|
518
|
+
dataOut = ''
|
519
|
+
else
|
520
|
+
raise ArgumentError, "invalid param :dataIn" unless dataIn.nil?
|
521
|
+
dataOut ||= ''
|
522
|
+
end
|
523
|
+
|
524
|
+
# reuse transfer struct to speed up transfer
|
525
|
+
@control_transfer ||= ControlTransfer.new dev_handle: self, allow_device_memory: allow_device_memory
|
526
|
+
tr = @control_transfer
|
527
|
+
tr.timeout = timeout
|
528
|
+
if dataIn
|
529
|
+
setup_data = [bmRequestType, bRequest, wValue, wIndex, dataIn].pack('CCvvv')
|
530
|
+
tr.alloc_buffer( dataIn + CONTROL_SETUP_SIZE, setup_data )
|
531
|
+
else
|
532
|
+
tr.buffer = [bmRequestType, bRequest, wValue, wIndex, dataOut.bytesize, dataOut].pack('CCvvva*')
|
533
|
+
end
|
534
|
+
|
535
|
+
submit_transfer(tr, dataIn, CONTROL_SETUP_SIZE, &block)
|
536
|
+
end
|
537
|
+
|
538
|
+
private
|
539
|
+
def submit_transfer(tr, dataIn, offset)
|
540
|
+
if block_given?
|
541
|
+
tr.submit! do
|
542
|
+
res = dataIn ? tr.actual_buffer(offset) : tr.actual_length
|
543
|
+
|
544
|
+
if tr.status==:TRANSFER_COMPLETED
|
545
|
+
yield res
|
546
|
+
else
|
547
|
+
exception = Transfer::TransferStatusToError[tr.status] || ERROR_OTHER
|
548
|
+
|
549
|
+
yield exception.new("error #{tr.status}", res)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
self
|
553
|
+
else
|
554
|
+
tr.submit_and_wait
|
555
|
+
|
556
|
+
res = dataIn ? tr.actual_buffer(offset) : tr.actual_length
|
557
|
+
|
558
|
+
unless tr.status==:TRANSFER_COMPLETED
|
559
|
+
raise((Transfer::TransferStatusToError[tr.status] || ERROR_OTHER).new("error #{tr.status}", res))
|
560
|
+
end
|
561
|
+
res
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
def endpoints_as_ffi_bytes(endpoints)
|
566
|
+
pEndpoints = FFI::MemoryPointer.new :char, endpoints.length
|
567
|
+
endpoints.each_with_index do |ep, epi|
|
568
|
+
ep = ep.bEndpointAddress if ep.respond_to? :bEndpointAddress
|
569
|
+
pEndpoints.put_uchar(epi, ep)
|
570
|
+
end
|
571
|
+
pEndpoints
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|