Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pid/vid getting in hid_enumerate() for Bluetooth devices #62

Closed
wants to merge 3 commits into from
Closed

Conversation

thp
Copy link
Contributor

@thp thp commented Jun 1, 2012

Searching for the USB host and extracting the vendor ID and product
ID from there only works for USB-based HID devices. In the case of
HID devices connected via Bluetooth, we would get the VID/PID of the
Bluetooth host adapter (if it's a USB device).

To work around this, we don't use the USB VID/PID, but extract the
information directly from the sysfs path - this will return the right
VID/PID for Bluetooth devices and USB devices.

Tested on Linux 3.2.0 for both USB and Bluetooth devices.

@thp
Copy link
Contributor Author

thp commented Jun 1, 2012

One remark: Right now, there's no checking for errors (e.g. when the sysfs path would not end in /hidraw/hidraw). One assumption is that all sysfs paths would be looking good when returned from udev_list_entry_get_name(). If this assumption isn't safe in your opinion, I'll be happy to add more error checking to get_pid_vid_from_sysfs_path().

@signal11
Copy link
Owner

signal11 commented Jun 1, 2012

Hi Thomas,

I like the idea but I wonder if there's a better way. Unfortunately I currently can't make my Bluetooth trackpad work with any of my Linux boxes to try anything out. My bluetooth used to work :( ....

Anyway, my idea would be to instead of doing string manipulations on the path, to maybe instead call udev_device_get_parent_with_subsystem_devtype() with subsystem of "hid" and devtype NULL[1] to get a handle to the HID device, then calling udev_device_get_sysname() to get the string with the VID/PID in it. I suppose I can try this out here on USB and see if it works and send you code to try on Bluetooth since I've all but given up on mine. Not sure what it is.... Maybe my new kernel with old userland, but it doesn't work on my other computer with Ubuntu 12.04. Strange.

[1] http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/libudev-udev-device.html#udev-device-get-parent-with-subsystem-devtype

@signal11
Copy link
Owner

signal11 commented Jun 1, 2012

Your patch removes the initialization for dev, which is used by the calls to copy_udev_string(). Does this not crash for you?

@signal11
Copy link
Owner

signal11 commented Jun 1, 2012

Also, based on what your udev/sysfs looks like, is there a good way to get the serial, manufacturer, and product strings from anywhere else in the sysfs tree for a bluetooth device?

@signal11
Copy link
Owner

signal11 commented Jun 2, 2012

I finally got my BT device to associate (on a different computer still.....)

It looks like there's no support for manufacturer and product strings currently in sysfs, but there is a hardware address. Perhaps we could make use of that somehow, maybe stick it in the serial number field or something. What do you think?

@signal11
Copy link
Owner

signal11 commented Jun 2, 2012

In your example directory name:
0005:054C:03D5.0007

The 0005 means bluetooth. 0003 would mean USB. So maybe we call udev_device_get_parent_with_subsystem_devtype() with "hid" and NULL, parse the bus type from the name, and then if USB (3), do what we do now. If not (5), then parse just the VID/PID like you suggested.

@thp
Copy link
Contributor Author

thp commented Jun 2, 2012

Hi Alan, thanks for your feedback! I've read up on udev a bit, and played with the pyudev bindings and with getting the "hid" parent as you suggested. I've come up with the following test program:

import pyudev
context = pyudev.Context()

for device in context.list_devices(subsystem='hidraw'):
    print 'device.sys_path =', device.sys_path
    print 'device.device_node =', device.device_node
    print 'device uevent attributes:'
    print device.attributes['uevent']

    print '-'*10

    parent = device.find_parent(subsystem='hid')
    print 'parent.sys_path =', parent.sys_path
    print 'parent uevent attributes:'
    print parent.attributes['uevent']
    print '\n'

Running this program on my computer gives the following result (hidraw3 = a Move controller connected via USB, hidraw6 = a Move controller connected via Bluetooth):

device.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-5/3-5:1.0/0003:05AC:8242.0001/hidraw/hidraw0
device.device_node = /dev/hidraw0
device uevent attributes:
MAJOR=250
MINOR=0
DEVNAME=hidraw0
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-5/3-5:1.0/0003:05AC:8242.0001
parent uevent attributes:
DRIVER=apple
HID_ID=0003:000005AC:00008242
HID_NAME=Apple Computer, Inc. IR Receiver
HID_PHYS=usb-0000:00:04.0-5/input0
HID_UNIQ=
MODALIAS=hid:b0003v000005ACp00008242


device.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-6/3-6:1.0/0003:05AC:0237.0002/hidraw/hidraw1
device.device_node = /dev/hidraw1
device uevent attributes:
MAJOR=250
MINOR=1
DEVNAME=hidraw1
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-6/3-6:1.0/0003:05AC:0237.0002
parent uevent attributes:
DRIVER=apple
HID_ID=0003:000005AC:00000237
HID_NAME=Apple Inc. Apple Internal Keyboard / Trackpad
HID_PHYS=usb-0000:00:04.0-6/input0
HID_UNIQ=
MODALIAS=hid:b0003v000005ACp00000237


device.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-6/3-6:1.1/0003:05AC:0237.0003/hidraw/hidraw2
device.device_node = /dev/hidraw2
device uevent attributes:
MAJOR=250
MINOR=2
DEVNAME=hidraw2
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:04.0/usb3/3-6/3-6:1.1/0003:05AC:0237.0003
parent uevent attributes:
DRIVER=apple
HID_ID=0003:000005AC:00000237
HID_NAME=Apple Inc. Apple Internal Keyboard / Trackpad
HID_PHYS=usb-0000:00:04.0-6/input1
HID_UNIQ=
MODALIAS=hid:b0003v000005ACp00000237


device.sys_path = /sys/devices/pci0000:00/0000:00:06.0/usb4/4-1/4-1.1/4-1.1:1.0/bluetooth/hci0/hci0:11/0005:054C:03D5.0007/hidraw/hidraw6
device.device_node = /dev/hidraw6
device uevent attributes:
MAJOR=250
MINOR=6
DEVNAME=hidraw6
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:06.0/usb4/4-1/4-1.1/4-1.1:1.0/bluetooth/hci0/hci0:11/0005:054C:03D5.0007
parent uevent attributes:
DRIVER=generic-bluetooth
HID_ID=0005:0000054C:000003D5
HID_NAME=Motion Controller
HID_PHYS=<removed from the comment, it is the host adapter BT address>
HID_UNIQ=<removed from the comment, it is the controller BT address>
MODALIAS=hid:b0005v0000054Cp000003D5


device.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.1/2-2.1:1.0/0003:054C:03D5.0004/hidraw/hidraw3
device.device_node = /dev/hidraw3
device uevent attributes:
MAJOR=250
MINOR=3
DEVNAME=hidraw3
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.1/2-2.1:1.0/0003:054C:03D5.0004
parent uevent attributes:
DRIVER=generic-usb
HID_ID=0003:0000054C:000003D5
HID_NAME=Sony Computer Entertainment Motion Controller
HID_PHYS=usb-0000:00:06.1-2.1/input0
HID_UNIQ=
MODALIAS=hid:b0003v0000054Cp000003D5


device.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.3/2-2.3.4/2-2.3.4:1.0/0003:046D:C404.0006/hidraw/hidraw5
device.device_node = /dev/hidraw5
device uevent attributes:
MAJOR=250
MINOR=5
DEVNAME=hidraw5
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.3/2-2.3.4/2-2.3.4:1.0/0003:046D:C404.0006
parent uevent attributes:
DRIVER=generic-usb
HID_ID=0003:0000046D:0000C404
HID_NAME=Logitech Trackball
HID_PHYS=usb-0000:00:06.1-2.3.4/input0
HID_UNIQ=
MODALIAS=hid:b0003v0000046Dp0000C404


device.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.4/2-2.4:1.0/0003:046A:0011.0005/hidraw/hidraw4
device.device_node = /dev/hidraw4
device uevent attributes:
MAJOR=250
MINOR=4
DEVNAME=hidraw4
----------
parent.sys_path = /sys/devices/pci0000:00/0000:00:06.1/usb2/2-2/2-2.4/2-2.4:1.0/0003:046A:0011.0005
parent uevent attributes:
DRIVER=generic-usb
HID_ID=0003:0000046A:00000011
HID_NAME=HID 046a:0011
HID_PHYS=usb-0000:00:06.1-2.4/input0
HID_UNIQ=
MODALIAS=hid:b0003v0000046Ap00000011

I'm not sure if the "uevent" attribute is always present there (can you think of a situation where it is not?). If it is always present, I'd suggest parsing out the PID/VID from the HID_ID= attribute there. Also, the HID_UNIQ= attribute contains the Bluetooth address, which should ideally be put into the serial number field (IIRC the hidapi also gives the Bluetooth address as serial number on Mac OS X).

I could update my patch to use the parent "hid" object and parse the serial and VID/PID out of the uevent attribute. I can also parse the bus type and use the current code for USB devices - would that work for you?

@thp
Copy link
Contributor Author

thp commented Jun 2, 2012

Updated patch. Things to do:

  • Indentation is inconsistent with other code in hid.c (do you have a "indent" command line that you use for your code)?
  • During testing I noted that hid_get_manufacturer_string(), hid_get_product_string() and hid_get_serial_number_string() still assume that there is an USB device. These probably need updating. I can get the serial number from the "hid" subsystem parent device, and I can get the product string from the "hid" subsystem as well (see HID_NAME above), but not sure where to get the manufacturer string for Bluetooth devices (I have to check what hidapi on OS X does in this case).

@thp
Copy link
Contributor Author

thp commented Jun 2, 2012

I've just rebooted into OS X and checked it out (for two different controllers, one connected via USB, one via Bluetooth):

  • Bluetooth:
    • serial number = Bluetooth address
    • manufacturer = "Unknown"
    • product = "Motion Controller"
  • USB:
    • serial number = ""
    • manufacturer = "Sony Computer Entertainment"
    • product = "Motion Controller"

Should I special-case the hid_get_manufacturer_string(), hid_get_product_string() and hid_get_serial_number_string() functions and look up the serial number and product ID with the new method ("hid" subsystem parent and "uevent" file) and return "Unknown" (or the empty string) for the manufacturer if the device is a Bluetooth device? If it is a USB device, we would simply use the existing method to get the strings.

@signal11
Copy link
Owner

signal11 commented Jun 3, 2012

Hi Thomas,

Good work. This is definitely in the right direction. Some comments:

  1. Make parse_uevent_info() take device_type as a parameter just like the other values. It was a bit confusing to me at first to see this. Possibly also return an error code to make sure it was parsed properly (because older versions of the Linux kernel (or newer) may not have this stuff laid out exactly the same.
  2. I'm trying to decide if it's ok to do the pointer offsetting for parsing HID_ID or whether it might be better to set up another strtok_r(), or whether it might be better still to just do a sscanf("HID_ID=%x:%x:%x", &bus, &vid, &pid) (and of course check the return value). The second two solutions insulate against the number of leading-zeroes changing in the future.
  3. It would be good to document the fact that the serial_number variable is utf-8. Maybe just calling it serial_number_utf8 is good enough.
  4. Stylistically, maybe it would be easier to, once it's determined what type of device it is, put all the bluetooth stuff together and put all the USB stuff together.
parse_uevent_info()
if (device_type == 3 /* 3=USB */) {
    //do all the USB stuff
}
else if (device_type == 5 /* 5=Bluetooth */) {
    // do all the Bluetooth stuff
}
  1. Don't be afraid to use all the horizontal space (like where you do a line break when you set the serial number). I didn't count the characters, but it looks less than 80. Maybe I'm wrong...
  2. For the hid_get_*_string() API functions, maybe it would be best to break out the udev string reading functionality into a reusable function which can be called by hid_enumerate() and also get_device_string().
  3. For the indentation, I don't use indent. I've never found a way to make it only do what I want it to do. It seems like instead of changing only what you want it to change, indent reads all the code, then writes it out according to its rules. A lot of my code doesn't conform to specific things which indent provides options for, so it always ends up munging things I don't want it to change. Maybe indent is better these days. I haven't tried it in 6 years. For the amount of code in this patch, I'd just do it by hand. The code in HIDAPI uses tab characters for indentation. Your editor should be able to go into that mode.

Thanks for taking a look at this. I think we're really close to having a good solution here.

Alan.

@thp
Copy link
Contributor Author

thp commented Jun 7, 2012

Hi Alan,

I've now fixed the points you mentioned, and make the code cleaner:

  • I found out that in linux/input.h, the bus types are defined as 3 and 5, so I'm using these symbolic names now
  • Instead of copying the device path (to cur_dev->path) "by hand", I've used strdup() - is that okay?

Still, for some parts I need your input:

  • At the point where I wrote "What if usb_dev is NULL here?" - do we have to check for that situation (and what should we do in this case) or can we just assume that there will always be a USB device if we found out (above) that the bus type is USB?
  • The code now always takes the VID/PID from the uevent device node, which simplifies the code. The thing is, if for some reason we can't parse the HID_ID in parse_uevent_info() (which is where we get the VID/PID now), we also cannot know (with the current method) if it's a USB device or Bluetooth device, so we fail earlier already (checking the result of parse_uevent_info()). If we found the bus type, we have also found the VID/PID from HID_ID.

I still have to implement the get_device_string part that we talked about above. I plan to make parse_uevent_info() more generic, so both functions can use its features, as you suggested.

Thanks for taking the time to review the patch and the feedback so far :)

@signal11
Copy link
Owner

Gar, I didn't realize comments on the commit would show up in this thread. Oh well, they also show up on the commit, so that you have context.

I think at this point, if we can't get the VID/PID from the uevent, we just fail for that device. I'm not sure how far we should be expected to go in order to insulate against changes we can't predict.

Thomas, thanks for your hard work on this. It is appreciated and I think we're getting close to having it done.

@thp
Copy link
Contributor Author

thp commented Jul 3, 2012

Updated merge request with your feedback. Please have a look. Right now, I don't have any Bluetooth device with me (will be back on the weekend and have access to some devices then), and I even though I ran Valgrind on it, I wasn't able to understand its output, so it would be great if you could double-check the free()s. Thanks :)

@thp
Copy link
Contributor Author

thp commented Jul 10, 2012

I've updated and tested the patch with my PS Move API library (which does enumeration of HID devices). Valgrind shows the following output:

==3581== LEAK SUMMARY:
==3581==    definitely lost: 0 bytes in 0 blocks
==3581==    indirectly lost: 0 bytes in 0 blocks
==3581==      possibly lost: 0 bytes in 0 blocks
==3581==    still reachable: 399 bytes in 3 blocks
==3581==         suppressed: 0 bytes in 0 blocks
==3581== 
==3581== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==3581== 
==3581== 1 errors in context 1 of 2:
==3581== Invalid read of size 8
==3581==    at 0x4100D2D: __wcscmp_sse2 (wcscmp-sse2.S:501)
==3581==    by 0x40384FD: hid_open (hid.c:532)
==3581==    by 0x403A9B9: psmove_connect_internal (psmove.c:397)
==3581==    by 0x403AB7C: psmove_connect_by_id (psmove.c:505)
==3581==    by 0x403ABEA: psmove_connect (psmove.c:527)
==3581==    by 0x40774D2: (below main) (libc-start.c:226)
==3581==  Address 0x42e9f78 is 0 bytes after a block of size 72 alloc'd
==3581==    at 0x402A5E6: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==3581==    by 0x40377F6: utf8_to_wchar_t (hid.c:90)
==3581==    by 0x4038003: hid_enumerate (hid.c:423)
==3581==    by 0x40384BF: hid_open (hid.c:526)
==3581==    by 0x403A9B9: psmove_connect_internal (psmove.c:397)
==3581==    by 0x403AB7C: psmove_connect_by_id (psmove.c:505)
==3581==    by 0x403ABEA: psmove_connect (psmove.c:527)
==3581==    by 0x40774D2: (below main) (libc-start.c:226)
==3581== 
==3581== 
==3581== 1 errors in context 2 of 2:
==3581== Invalid read of size 8
==3581==    at 0x4100D24: __wcscmp_sse2 (wcscmp-sse2.S:499)
==3581==    by 0x40384FD: hid_open (hid.c:532)
==3581==    by 0x403A9B9: psmove_connect_internal (psmove.c:397)
==3581==    by 0x403AB7C: psmove_connect_by_id (psmove.c:505)
==3581==    by 0x403ABEA: psmove_connect (psmove.c:527)
==3581==    by 0x40774D2: (below main) (libc-start.c:226)
==3581==  Address 0x42c3b38 is 0 bytes after a block of size 72 alloc'd
==3581==    at 0x402A5E6: calloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==3581==    by 0x40377F6: utf8_to_wchar_t (hid.c:90)
==3581==    by 0x4038003: hid_enumerate (hid.c:423)
==3581==    by 0x403AB23: psmove_connect_by_id (psmove.c:498)
==3581==    by 0x403ABEA: psmove_connect (psmove.c:527)
==3581==    by 0x40774D2: (below main) (libc-start.c:226)
==3581== 
==3581== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

@signal11
Copy link
Owner

ok, I think those two errors are bugs in wcscmp:
https://bugzilla.redhat.com/show_bug.cgi?id=755242

@signal11
Copy link
Owner

Change it so that hid_init() and hid_exit() don't move. This will make the commit smaller and cleaner.

Why does linux/input.h now need to be included?

Add a comment to utf8_to_wchar_t() indicating that the result must be free()d.

In get_device_string() it first checks for a parent sysfs device of type "hid", and parses data from it. If it can't find a parent of type "hid" it then does the old method of finding the parent USB device. There are a couple problems here. The first is that it will always go into the if (hid_dev) section because there will always be a parent "hid" node. The problem with doing this is that the "hid" device doesn't have the Manufacturer string in it, and we're faking the product string with the HID_NAME uevent property (which actually contains both manufacturer and product). For USB devices, which do have proper Manufacturer strings, this will be a regression. We should key off the bus_type here, just like in hid_enumerate(). If it's a BT device, then use what's in the "hid" node, and if it's a USB device, then look for the parent USB device. In fact, maybe you could move most of the logic from that section in hid_enumerate() into get_device_string().

Also in this function, I can see why you need to do the hard-coded string match for "manufacturer", etc, but since that string is now in two places, let's make it a #define up at the top and use it in get_device_string() and also in hid_get_*_string() functions. Maybe there's still a better way to do it (pass an enum token in instead of a string or something).

@thp
Copy link
Contributor Author

thp commented Jul 12, 2012

The moving of the functions was accidental, it's fixed now.

The linux/input.h include is used for the constants BUS_USB and BUS_BLUETOOTH.

I've also added the comment about free'ing the result of the utf8_to_wchar_t() function.

I also reworked get_device_string to fallback to the old code in case the device isn't a Bluetooth device.

For the string-getting functions, I've now introduced a new enum (enum device_string_id) and I've also #defined the strings that are used as symbolic names, because I also use them in the enumerate function too.

@signal11
Copy link
Owner

I made a couple of comments about one more thing I'd like to change.

Other than that I think we're there. Good work.

@thp
Copy link
Contributor Author

thp commented Jul 13, 2012

I've made the changes. One thing I've been wondering: The device_string_names array is constant, so one would have to name it DEVICE_STRING_NAMES, but that looks kind of weird. Is the lowercase version okay for you?

@signal11
Copy link
Owner

Lowercase is fine with me.

@thp
Copy link
Contributor Author

thp commented Jul 14, 2012

Ok then :) So, anything left to do or can this go in?

@thp
Copy link
Contributor Author

thp commented Jul 16, 2012

Whoops - I just found two stray "printf"s that should not be there obviously. Will submit an updated patch in just a minute.

@signal11
Copy link
Owner

I made a couple changes, checkout the branch named thp_bluetooth and let me know if you agree with the changes and if it works for your device. It seems to work here for my USB device.

If you're good with it, I'm ready to squash the commits and push.

btw: that branch is based on before you took the printf()s out. I diffed the diffs, and that was indeed the only change between your last two revisions.

@thp
Copy link
Contributor Author

thp commented Jul 17, 2012

Thanks for the update. In the case DEVICE_STRING_SERIAL: it seems like you've forgotten to assign the result of mbstowcs(string, serial_number_utf8, maxlen); to ret (compared to case DEVICE_STRING_PRODUCT:). There's also another occasion of the same assignment that you probably want to have fixed - see the updated commit.

I've tested it here and it was able to successfully detect a HID device connected via USB and also via Bluetooth, so from my perspective, this can go in.

@signal11
Copy link
Owner

ok looks good. Good catch on the other mbstowcs().

@signal11
Copy link
Owner

Ok, so I really screwed up here. I inadvertently pushed the change last night with the printf() stuff in it. Since it's really bad form to rebase stuff that's been upstream, I pushed my commit from yesterday and yours from today on top of it.

It really hacks me off that I screwed this up, since I put so much effort into trying to get this to go in as one clean commit.

Oh well. It's done now so, so we move on.

pushed:
36f7dec
4835261
807afbc

Thanks for doing this, and thanks for your patience.

@signal11 signal11 closed this Jul 18, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants