Two HID joysticks from one device
Posted: Mon Apr 12, 2010 8:37 pm
I am trying to create a device that appears to the computer as two separate joysticks. I have it working in Windows but not in Linux.
Windows can see two separate joysticks in the game controller applet, but Linux only sees one in /dev/input. I know Linux can show two as I have a dual Playstation to USB converter which appears as two separate devices in /dev/input.
I tried cloning the converter's HID report descriptor but it didn't seem to help. I also tried cloning as much of it's USB device descriptor as possible but that didn't make it work either. I am at a loss to explain why it doesn't work in Linux because from what I can tell there is nothing obviously different about the converter and my hardware.
For reference, my HID report (using stock V-USB descriptors for everything else):
The output of lsusb for my device:
And the output of lsusb for the dual PSX converter (HID report is different, I know, I have simplified it to the above to try and remove as many variables as possible):
I see there are a couple of other threads along similar lines but none seem to have reached any kind of useful resolution or mention Linux support. As I say, Windows is fine.
Any help would be much appreciated.
Windows can see two separate joysticks in the game controller applet, but Linux only sees one in /dev/input. I know Linux can show two as I have a dual Playstation to USB converter which appears as two separate devices in /dev/input.
I tried cloning the converter's HID report descriptor but it didn't seem to help. I also tried cloning as much of it's USB device descriptor as possible but that didn't make it work either. I am at a loss to explain why it doesn't work in Linux because from what I can tell there is nothing obviously different about the converter and my hardware.
For reference, my HID report (using stock V-USB descriptors for everything else):
Code: Select all
PROGMEM char usbHidReportDescriptor[90] = {
/* Controller and report_id 1 */
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0xa1, 0x02, // COLLECTION (Physical)
0x09, 0x01, // USAGE (Pointer)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 1, // USAGE_MINIMUM (Button 1)
0x29, 8, // USAGE_MAXIMUM (Button 8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 1, // REPORT_SIZE (1)
0x95, 8, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
/* Controller and report_id 2 */
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x02, // REPORT_ID (2)
0xa1, 0x02, // COLLECTION (Physical)
0x09, 0x01, // USAGE (Pointer)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 1, // USAGE_MINIMUM (Button 1)
0x29, 8, // USAGE_MAXIMUM (Button 8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 1, // REPORT_SIZE (1)
0x95, 8, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
};
The output of lsusb for my device:
Code: Select all
Bus 002 Device 004: ID 8282:2101
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x8282
idProduct 0x2101
bcdDevice 4.01
iManufacturer 1 MoJo
iProduct 2 Retro Adapter
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 250mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 90
Report Descriptor: (length is 90)
Item(Global): Usage Page, data= [ 0x01 ] 1
Generic Desktop Controls
Item(Local ): Usage, data= [ 0x04 ] 4
Joystick
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Local ): Usage, data= [ 0x01 ] 1
Pointer
Item(Main ): Collection, data= [ 0x00 ] 0
Physical
Item(Global): Report ID, data= [ 0x01 ] 1
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x31 ] 49
Direction-Y
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x02 ] 2
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Usage Page, data= [ 0x09 ] 9
Buttons
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
Button 1 (Primary)
Item(Local ): Usage Maximum, data= [ 0x08 ] 8
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x08 ] 8
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): End Collection, data=none
Item(Global): Usage Page, data= [ 0x01 ] 1
Generic Desktop Controls
Item(Local ): Usage, data= [ 0x04 ] 4
Joystick
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Local ): Usage, data= [ 0x01 ] 1
Pointer
Item(Main ): Collection, data= [ 0x00 ] 0
Physical
Item(Global): Report ID, data= [ 0x02 ] 2
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x31 ] 49
Direction-Y
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x02 ] 2
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Usage Page, data= [ 0x09 ] 9
Buttons
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
Button 1 (Primary)
Item(Local ): Usage Maximum, data= [ 0x08 ] 8
(null)
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x08 ] 8
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): End Collection, data=none
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 10
Device Status: 0x0000
(Bus Powered)
And the output of lsusb for the dual PSX converter (HID report is different, I know, I have simplified it to the above to try and remove as many variables as possible):
Code: Select all
Bus 002 Device 003: ID 0810:0001 Personal Communication Systems, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0810 Personal Communication Systems, Inc.
idProduct 0x0001
bcdDevice 1.06
iManufacturer 0
iProduct 2 Twin USB Joystick
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 34
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
bCountryCode 33 US
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 202
Report Descriptor: (length is 202)
Item(Global): Usage Page, data= [ 0x01 ] 1
Generic Desktop Controls
Item(Local ): Usage, data= [ 0x04 ] 4
Joystick
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Global): Report ID, data= [ 0x01 ] 1
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Physical Minimum, data= [ 0x00 ] 0
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x32 ] 50
Direction-Z
Item(Local ): Usage, data= [ 0x35 ] 53
Rotate-Z
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x31 ] 49
Direction-Y
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Report Size, data= [ 0x04 ] 4
Item(Global): Report Count, data= [ 0x01 ] 1
Item(Global): Logical Maximum, data= [ 0x07 ] 7
Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315
Item(Global): Unit, data= [ 0x14 ] 20
System: English Rotation, Unit: Degrees
Item(Local ): Usage, data= [ 0x39 ] 57
Hat Switch
Item(Main ): Input, data= [ 0x42 ] 66
Data Variable Absolute No_Wrap Linear
Preferred_State Null_State Non_Volatile Bitfield
Item(Global): Unit, data= [ 0x00 ] 0
System: None, Unit: (None)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x0c ] 12
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Global): Usage Page, data= [ 0x09 ] 9
Buttons
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
Button 1 (Primary)
Item(Local ): Usage Maximum, data= [ 0x0c ] 12
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
(null)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x08 ] 8
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Local ): Usage, data= [ 0x01 ] 1
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x02 ] 2
(null)
Item(Main ): Output, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): End Collection, data=none
Item(Global): Usage Page, data= [ 0x01 ] 1
Generic Desktop Controls
Item(Local ): Usage, data= [ 0x04 ] 4
Joystick
Item(Main ): Collection, data= [ 0x01 ] 1
Application
Item(Global): Report ID, data= [ 0x02 ] 2
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Logical Minimum, data= [ 0x00 ] 0
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Physical Minimum, data= [ 0x00 ] 0
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x32 ] 50
Direction-Z
Item(Local ): Usage, data= [ 0x35 ] 53
Rotate-Z
Item(Local ): Usage, data= [ 0x30 ] 48
Direction-X
Item(Local ): Usage, data= [ 0x31 ] 49
Direction-Y
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Report Size, data= [ 0x04 ] 4
Item(Global): Report Count, data= [ 0x01 ] 1
Item(Global): Logical Maximum, data= [ 0x07 ] 7
Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315
Item(Global): Unit, data= [ 0x14 ] 20
System: English Rotation, Unit: Degrees
Item(Local ): Usage, data= [ 0x39 ] 57
Hat Switch
Item(Main ): Input, data= [ 0x42 ] 66
Data Variable Absolute No_Wrap Linear
Preferred_State Null_State Non_Volatile Bitfield
Item(Global): Unit, data= [ 0x00 ] 0
System: None, Unit: (None)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x0c ] 12
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Global): Usage Page, data= [ 0x09 ] 9
Buttons
Item(Local ): Usage Minimum, data= [ 0x01 ] 1
Button 1 (Primary)
Item(Local ): Usage Maximum, data= [ 0x0c ] 12
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
(null)
Item(Global): Report Size, data= [ 0x01 ] 1
Item(Global): Report Count, data= [ 0x08 ] 8
Item(Global): Logical Maximum, data= [ 0x01 ] 1
Item(Global): Physical Maximum, data= [ 0x01 ] 1
Item(Local ): Usage, data= [ 0x01 ] 1
(null)
Item(Main ): Input, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): Collection, data= [ 0x02 ] 2
Logical
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
Item(Local ): Usage, data= [ 0x02 ] 2
(null)
Item(Main ): Output, data= [ 0x02 ] 2
Data Variable Absolute No_Wrap Linear
Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Item(Main ): End Collection, data=none
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 10
Device Status: 0x0000
(Bus Powered)
I see there are a couple of other threads along similar lines but none seem to have reached any kind of useful resolution or mention Linux support. As I say, Windows is fine.
Any help would be much appreciated.