ERROR_IO when writing to device, but only sometimes?
xxx opened this issue · 7 comments
I'm working on a little driver to set key colors and actuation heights on a Realforce RGB keyboard, but am seeing inconsistent results, which I'm sure is something I'm missing, but I cannot figure out what it is.
Most of the time when I run the code below, it fails with LIBUSB::ERROR_IO
, and does not make it far enough to be seen by Wireshark. I see the same behavior whether running as root or normal user, and with both Ruby 2.3.3 and 2.4.1.
It fails far more often than it works, but when it gets into a working state, it stays that way until I unplug and replug the device.
Is there anything obvious I'm missing here? It's clear from various searches that getting devices into the correct state for reading and writing is something that others struggle with as well.
system uname (Ubuntu 17.04):
Linux vatican 4.10.0-26-generic #30-Ubuntu SMP Tue Jun 27 09:30:12 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
the code:
require 'libusb'
require 'pry'
VENDOR_ID = 0x0853
PRODUCT_ID = 0x013a
#ENDPOINT = 0x04
data = "\xaa\xaa\x62\x00\x04\x00\xff\x00\x00".force_encoding(Encoding::BINARY)
usb = LIBUSB::Context.new
usb.debug = 10
devices = usb.devices(idVendor: VENDOR_ID, idProduct: PRODUCT_ID)
device = devices.first
raise 'No device found' unless device
begin
endpoint_in = device.endpoints.find { |ep| !(ep.bEndpointAddress & LIBUSB::ENDPOINT_IN).zero? }
endpoint_out = device.endpoints.find { |ep| (ep.bEndpointAddress & LIBUSB::ENDPOINT_IN).zero? }
# binding.pry
h = device.open
h.auto_detach_kernel_driver = true
h.claim_interface(0)
h.clear_halt endpoint_in
response = h.interrupt_transfer(
endpoint: endpoint_out,
dataOut: data,
timeout: 10000
)
p response
ensure
h.release_interface(0)
h.close
end
When it fails:
libusb: debug [libusb_get_device_list]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_open] open 1.47
libusb: debug [usbi_add_pollfd] add fd 14 events 4
libusb: debug [libusb_claim_interface] interface 0
libusb: debug [libusb_clear_halt] endpoint 81
libusb: debug [libusb_alloc_transfer] transfer 0x55d35fc5d7e0
libusb: debug [libusb_submit_transfer] transfer 0x55d35fc5d7e0
libusb: debug [add_to_flying_list] arm timerfd for timeout in 10000ms (first in line)
libusb: debug [submit_bulk_transfer] need 1 urbs for new transfer with length 9
libusb: error [submit_bulk_transfer] submiturb failed error -1 errno=16
libusb: debug [submit_bulk_transfer] first URB failed, easy peasy
libusb: debug [disarm_timerfd]
libusb: debug [libusb_release_interface] interface 0
libusb: debug [libusb_close]
libusb: debug [usbi_remove_pollfd] remove fd 14
/home/mpd/.rvm/gems/ruby-2.3.3/gems/libusb-0.6.2-x86_64-linux/lib/libusb/constants.rb:62:in `raise_error': LIBUSB::ERROR_IO in libusb_submit_transfer (LIBUSB::ERROR_IO)
from /home/mpd/.rvm/gems/ruby-2.3.3/gems/libusb-0.6.2-x86_64-linux/lib/libusb/transfer.rb:202:in `submit!'
from /home/mpd/.rvm/gems/ruby-2.3.3/gems/libusb-0.6.2-x86_64-linux/lib/libusb/transfer.rb:229:in `submit_and_wait'
from /home/mpd/.rvm/gems/ruby-2.3.3/gems/libusb-0.6.2-x86_64-linux/lib/libusb/dev_handle.rb:544:in `submit_transfer'
from /home/mpd/.rvm/gems/ruby-2.3.3/gems/libusb-0.6.2-x86_64-linux/lib/libusb/dev_handle.rb:465:in `interrupt_transfer'
from usb.rb:35:in `<main>'
libusb: debug [libusb_free_transfer] transfer 0x55d35fc5d7e0
When it works:
libusb: debug [libusb_get_device_list]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_device_descriptor]
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_get_config_descriptor] index 0
libusb: debug [libusb_open] open 1.43
libusb: debug [usbi_add_pollfd] add fd 14 events 4
libusb: debug [libusb_claim_interface] interface 0
libusb: debug [libusb_clear_halt] endpoint 81
libusb: debug [libusb_alloc_transfer] transfer 0x563855794c20
libusb: debug [libusb_submit_transfer] transfer 0x563855794c20
libusb: debug [add_to_flying_list] arm timerfd for timeout in 10000ms (first in line)
libusb: debug [submit_bulk_transfer] need 1 urbs for new transfer with length 9
libusb: debug [libusb_handle_events_timeout_completed] doing our own event handling
libusb: debug [handle_events] poll fds modified, reallocating
libusb: debug [handle_events] poll() 3 fds with timeout in 60000ms
libusb: debug [handle_events] poll() returned 1
libusb: debug [reap_for_handle] urb type=1 status=0 transferred=9
libusb: debug [handle_bulk_completion] handling completion status 0 of bulk urb 1/1
libusb: debug [handle_bulk_completion] last URB in transfer --> complete!
libusb: debug [disarm_timerfd]
libusb: debug [usbi_handle_transfer_completion] transfer 0x563855794c20 has callback 0x7f3bdb7b5000
libusb: debug [libusb_release_interface] interface 0
libusb: debug [libusb_close]
libusb: debug [usbi_remove_pollfd] remove fd 14
libusb: debug [libusb_free_transfer] transfer 0x563855794c20
Full lsusb
output for this device:
Bus 001 Device 016: ID 0853:013a Topre Corporation
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.01
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0853 Topre Corporation
idProduct 0x013a
bcdDevice 0.01
iManufacturer 1 Topre
iProduct 2 BackLightKB
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 91
bNumInterfaces 3
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 1 Boot Interface Subclass
bInterfaceProtocol 1 Keyboard
iInterface 3 (error)
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 65
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 4 (error)
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 119
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 2
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 34
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x04 EP 4 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Device Status: 0x0000
(Bus Powered)
Sorry I can not really help you. I didn't use a device with interrupt transfers so far (but I know others did). Recovering the device from failures is often an issue. Maybe you need some control transfer(s) to get the device into the required state - just guessing...
One small optimization: you can write endpoint_in = device.endpoints.find { |ep| ep.direction == :in }
Ok, thank you for responding. I'll close this, but will try to update with the solution if I find it for the sake of future searches.
It looks like I needed to explicitly set the configuration of the handle before claiming the interface. It throws an exception if it's already set, but I don't know how to test for it, so I'm using a rescue nil
until I find out how. Seems to be possible in the C library but doesn't look like it's exposed here?
Anyway, working code is below:
require 'libusb'
require 'pry'
VENDOR_ID = 0x0853
PRODUCT_ID = 0x013a
ENDPOINT = 0x04
data = "\xaa\xaa\x62\x00\x04\x00\xff\x00\x00".force_encoding(Encoding::BINARY)
usb = LIBUSB::Context.new
usb.debug = 4
devices = usb.devices(idVendor: VENDOR_ID, idProduct: PRODUCT_ID)
device = devices.first
raise 'No device found' unless device
handle = nil
begin
handle = device.open
handle.auto_detach_kernel_driver = true
handle.set_configuration(1) rescue nil
handle.claim_interface(0)
response = handle.interrupt_transfer(
endpoint: ENDPOINT,
dataOut: data,
timeout: 10000
)
p response
ensure
handle.release_interface(0)
handle.close
end
I should also add that I had to do a little bit of work with udev config on ubuntu linux to get it working as a non-root user and stop usbhid from taking the device from me. I created /etc/udev/rules.d/99-topre.rules
with the following content:
SUBSYSTEM=="usb", ATTRS{idVendor}=="0853", ATTRS{idProduct}=="013a", MODE="0666", GROUP="plugdev"
ATTRS{idVendor}=="0853", ATTRS{idProduct}=="013a", RUN="/bin/sh -c 'echo -n $kernel >/sys/bus/usb/drivers/usbhid/unbind'"
You could try set_configuration(nil)
to get an unconfigured state before.
I gave that a shot, but it just throws ERROR_BUSY