/dev/usb/hid (v4)

From WiiBrew
< /dev‎ | usb
Jump to navigation Jump to search

/dev/usb/hid is used as an alternative interface to talk to USB HID devices such as the Rock Band instruments, USB keyboard or HID compatible game controller. There are at least 2 different versions which use different IOCTLs; only the earlier version (version 4) is documented here. It is found in IOS37, 60 and possibly some others.

List of IOCTLs

All memory should be allocated from MEM2 and 32-byte aligned. This structure is used for most commands:

// all multi-byte values are big endian
typedef struct {
	u8 padding[16]; // anything you want can go here
	s32 device_no;
	union {
		struct {
			u8 bmRequestType;
			u8 bmRequest;
			u16 wValue;
			u16 wIndex;
			u16 wLength;
		} control;
		struct {
			u32 endpoint;
			u32 dLength;
		} interrupt;
		struct {
			u8 bIndex;
		} string;
	};
	void *data; // virtual pointer, not physical!
} req_args; // 32 bytes
Request number Name Input Output Notes
0 GetDeviceChange none 0x600 bytes of device descriptors Returns when a HID device is inserted/removed (or immediately when called for the first time). The return value will be -1 if forced to return by calling the Shutdown IOCTL.
1 SetSuspend 8 bytes none Not actually implemented in IOS, returns 0 regardless of arguments
2 ControlMessage 32 bytes of req_args none Read/Write a USB control message. device_no, bmRequestType, bmRequest, wValue, wIndex and wLength must be set (and data if wLength!=0).
3 InterruptMessage(IN) 32 bytes of req_args none Read an interrupt message from an endpoint. device_no, endpoint, dLength and data must be set.
4 InterruptMessage(OUT) 32 bytes of req_args none Send an interrupt message to an endpoint. device_no, endpoint, dLength and data must be set.
5 GetUSString 32 bytes of req_args none Retrieves a descriptor string using language 0x0409(US). device_no, bIndex and data must be set. The returned string will be converted from unicode to char, data should be 255 bytes long to allow for the maximum length string. The actual length will be the return value.
6 GetVersion none 32 bytes (unused) Gets the version of the /dev/usb/hid module (should return 0x40001).
7 Shutdown none none Forces any pending GetDeviceChange requests to return immediately.
8 CancelInterrupt 8 bytes none Cancels any pending interrupt transfers for an endpoint.

The first 4 bytes are the device ID. The 5th byte is the endpoint.

Detailed Description

Unlike the regular IOS USB interface, all IPC commands are done via the /dev/usb/hid fd. There is no IOCTL to get a list of currently available devices, but there is an IOCTL that returns when a HID device is inserted or removed (it will also return immediately the first time it is called after IOS is loaded). It can also be forced to return immediately if the shutdown IOCTL is used, to ensure no dangling requests are left when your program exits.

GetDeviceChange

void *device_desc = memalign(32, 0x600); // should be from MEM2
ios_ioctl_async(fd, /*GetDeviceChange*/0, NULL, 0, device_desc, 0x600, cb, device_desc); // cb will be called when there is a device change, with the device_desc buffer as a parameter

The device_desc buffer will contain a set of descriptors for each device. At the beginning of each device's section will be two 4 bytes values, containing the total size of this device's data and the device's id number (used for device_no in req_args). The size of each individual descriptor within the data is padded to 4 bytes, the total size includes this padding. A size value of 0xFFFFFFFF indicates there are no more devices. For example here is what is returned when a Rock Band music keyboard is attached:

00000000  00 00 00 44 00 00 00 00 12 01 01 10 00 00 00 08
00000010  1B AD 33 30 00 05 00 00 00 01 00 00 09 02 00 20
00000020  01 01 00 80 32 00 00 00 09 04 00 00 02 03 00 00
00000030  00 00 00 00 07 05 02 03 00 40 01 00 07 05 81 03
00000040  00 40 0A 00 FF FF FF FF 00 00 00 00 00 00 00 00
... 0x5B0 bytes unused

Breaking this down into 4-bytes words, it can be parsed as follows:

u32 *desc = (u32*)device_desc;
desc[0x00] = 0x44;       // size of this device's data
desc[0x01] = 0;          // this device's id (used to issue commands to this device)
// DEVICE DESCRIPTOR
desc[0x02] = 0x12010110; // bLength=18, bDescriptorType=01, bcdUSB=1.10
desc[0x03] = 0x00000008; // bDeviceClass=0, bDeviceSubClass=0, bDeviceProtocol=0, bMaxPacketSize=8
desc[0x04] = 0x1BAD3330; // VID=0x1BAD, PID=0x3330
desc[0x05] = 0x00050000; // bcdDevice=5, iManufacturer=0, iProduct=0
desc[0x06] = 0x00010000; // iSerialNumber=0, bNumConfigurations=1, 2 bytes padding
// CONFIGURATION DESCRIPTOR
desc[0x07] = 0x09020020; // bLength=9, bDescriptorType=2, wTotalLength=32
desc[0x08] = 0x01010080; // bNumInterfaces=1, bConfigurationValue=1, iConfiguration=0, bmAttributes=0x80
desc[0x09] = 0x32000000; // bMaxPower=100ma (?), 3 bytes padding
// INTERFACE DESCRIPTOR
desc[0x0A] = 0x09040000; // bLength=9, bDescriptorType=4, bInterfaceNumber=0, bAlternateSetting=0
desc[0x0B] = 0x02030000; // bNumEndpoints=2, bInterfaceClass=3(HID), bInterfaceSubClass=0, bInterfaceProtocol=0
desc[0x0C] = 0x00000000; // iInterface, 3 bytes padding
// ENDPOINT DESCRIPTOR (OUT)
desc[0x0D] = 0x07050203; // bLength=7, bDescriptorType=5, bEndpointAddress=0x02, bmAttributes=0x03(Interrupt)
desc[0x0E] = 0x00400100; // wMaxPacketSize=64, bInterval=1, 1 byte padding
// ENDPOINT DESCRIPTOR (IN)
desc[0x0F] = 0x07058103; // bLength=7, bDescriptorType=5, bEndpointAddress=0x81, bmAttributes=0x03(Interrupt)
desc[0x10] = 0x00400A00; // wMaxPacketSize=64, bInterval=10, 1 byte padding
// total size = 0x11*sizeof(u32) = 0x44. End of first device's data.
desc[0x11] = 0xFFFFFFFF; // no more devices

SetSuspend

This does nothing; supposedly it suspends or resumes a device, but it's not implemented in the IOS code.

ControlMessage

Issues a control message to a device. Takes a filled out req_args struct as a parameter; device_no specifies the target device while control.bmRequestType, control.bmRequest, control.wValue, control.wIndex and control.wLength should be filled according to the message being sent. If there is extra input/output information associated with the message it should be pointed to by data (control.wLength should specify the length as per the USB standard).

InterruptMessage(IN)

Fetches an interrupt message from the device. Takes a filled out req_args struct as a parameter; device_no specifies the target device, interrupt.endpoint specifies which interface endpoint should be used for the transfer, interrupt.dLength specifies the length of the transfer and data points to a destination buffer. The specified endpoint should be an IN endpoint.

InterruptMessage(OUT)

Sends an interrupt message to the device. Same as above, but data points to a source buffer and the specified endpoint should be an OUT endpoint.

GetUSString

Requests the specified string descriptor from the device and converts it from unicode. Takes a filled out req_args struct as a parameter; device_no specifies the target device, string.bIndex is the index of the string descriptor and data is the destination buffer. Data should be at least 255 bytes long to allow for the longest possible string. Any characters that cannot be represented correctly by a single byte will be translated to '?'. The return value specifies the length of the returned string.

GetVersion

Retrieves the version of the /dev/usb/hid interface; currently this should return 0x40001. The output buffer is not used.

Shutdown

Cancels a pending GetDeviceChange request, making it return immediately (the returned value will be -1). This is used to "clean up" the IOS state.

CancelInterrupt

Similar to Shutdown, but forces a pending InterruptMessage IOCTL to return immediately.