ATV_IR_Device - lemonjesus/atv-bootloader GitHub Wiki
AppleTV Internal USB Controller Info
The AppleTV IR receiver appears as a USB-based human interface device (HID). Similar to a USB mouse or keyboard it uses an interrupt IN endpoint to send information to the host computer when some action occurs such as pressing a button on the Apple IR remote.
USB HID devices have the following requirements
- A HID must have an interrupt IN endpoint for sending periodic data to the host. An interrupt OUT endpoint for receiving periodic data from the host is optional
- A HID must contain a class descriptor and one or more report descriptors
- A HID must support the HID-specific control request Get_Report and may support the optional request Set_Report
For interrupt IN transfers, the device must place the report data in the interrupt endpoint's buffer and enable the endpoint.
The AppleTV IR HID device is defined by this report descriptor. This is taken from "irkeyboardemu" (bholland/bikedude880) and corrected. The descriptor in "irkeyboardemu" is garbled near the end and does not decode correctly but this will not effect IR functions. Here is the full and corrected and decoded HID report descriptor.
unsigned char atv_hid_report_descriptor[] = {
0x05, 0x0c, /* USAGE PAGE (Consumer) */
0x09, 0x01, /* USAGE (Pointer) */
0xa1, 0x01, /* COLLECTION (Application) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x01, /* LOGICAL MINIMUM (1) */
0x25, 0x02, /* LOGIXAL MAXIMUM (2) */
0x85, 0x01, /* ReportID (1) */
0x09, 0xff, /* Reserved */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x02, /* ReportID (2) */
0x09, 0xb4, /* Rewind */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x03, /* ReportID (3) */
0x09, 0xb3, /* Fast Forward */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x04, /* ReportID (4) */
0x09, 0x40, /* Menu */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x05, 0x01, /* USAGE PAGE (Generic Desktop) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x01, /* LOGICAL MINIMUM (1) */
0x25, 0x04, /* LOGIXAL MAXIMUM (4) */
0x85, 0x05, /* ReportID (5) */
0x09, 0xff, /* Reserved */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x06, /* ReportID (6) */
0x09, 0x86, /* System App Menu */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x07, /* ReportID (7) */
0x09, 0x89, /* System Menu Select */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x08, /* ReportID (8) */
0x09, 0x8a, /* System Menu Right */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x09, /* ReportID (9) */
0x09, 0x8b, /* System Menu Left */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x01, /* LOGICAL MINIMUM (1) */
0x25, 0x02, /* LOGIXAL MAXIMUM (2) */
0x85, 0x0a, /* ReportID (10) */
0x09, 0xff, /* Reserved */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x0b, /* ReportID (11) */
0x09, 0x8c, /* System Menu Up */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x0c, /* ReportID (12) */
0x09, 0x8d, /* System Menu Down */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */
0x05, 0xff, /* USAGE PAGE (Vender) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x01, /* LOGICAL MINIMUM (1) */
0x25, 0x04, /* LOGIXAL MAXIMUM (4) */
0x85, 0x0d, /* ReportID (13) */
0x09, 0xff, /* Reserved */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x0e, /* ReportID (14) */
0x09, 0x20, /* Vender 0x20 */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x0f, /* ReportID (15) */
0x09, 0x21, /* Vender 0x21 */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x10, /* ReportID (16) */
0x09, 0x22, /* Vender 0x22 */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0x01, /* LOGIXAL MAXIMUM (1) */
0x85, 0x11, /* ReportID (17) */
0x09, 0x23, /* Vender 0x23 */
0x81, 0x06, /* INPUT (Data, Variable, Relative) */
0x05, 0x06, /* USAGE PAGE (Generic Device Controls) */
0x75, 0x08, /* REPORT SIZE (8) */
0x95, 0x01, /* REPORT COUNT (1) */
0x15, 0x00, /* LOGICAL MINIMUM (0) */
0x25, 0xff, /* LOGIXAL MAXIMUM (255) */
0x85, 0x12, /* ReportID (18) */
0x09, 0x22, /* Wireless ID */
0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ 14
0xc0 /* END */ 1
};
Pulled from www.osxbook.com The usage table information for this device includes usages in the Generic Desktop Page (page ID 0x01), the Generic Device Controls Page (page ID 0x06), the Consumer Page (page ID 0x0C), and a vendor-defined page (page ID 0xff). In detail, there are;
- Consumer -- includes on/off (up/down in this case) controls with the usage IDs 0x40 (Menu), 0xb3 (Fast Forward), and 0xb4 (Rewind).
- Vendor -- includes usage IDs 0x20, 0x21, 0x22, and 0x23.
- Generic Desktop -- includes one-shot controls (OSC) with usage IDs 0x86 (System App Menu), 0x89 (System Menu Select), 0x8a (System Menu Right), 0x8b (System Menu Left), 0x8c (System Menu Up), and 0x8d (System Menu Down).
- Generic Device Controls -- includes a dynamic value with the usage ID ID 0x22 (Wireless ID), which identifies a wireless device in a wireless subsystem.
Info from Linux, "lsusb -v", if "Report Descriptor" shows as " UNAVAILABLE ", then "sudo modprobe -r usbhid" to expose it. Note that Linux only reports the "consumer" page.
Bus 001 Device 002: ID 05ac:8241 Apple Computer, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x05ac Apple Computer, Inc.
idProduct 0x8241
bcdDevice 2.42
iManufacturer 1 Apple Computer, Inc.
iProduct 2 IR Receiver
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 59
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 1 Apple Computer, Inc.
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 100mA
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.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 42
Report Descriptor: (length is 42)
Item(Global): Usage Page, data= [ 0x0c ] 12
Consumer
Item(Local ): Usage, data= [ 0x01 ] 1
Consumer Control
Item(Main ): Collection, data= [ 0x01 ] 1
Application
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= [ 0x04 ] 4
Item(Global): Report ID, data= [ 0x24 ] 36
Item(Local ): Usage, data= [ 0x00 ] 0
Unassigned
Item(Main ): Input, data= [ 0x22 ] 34
Data Variable Absolute No_Wrap Linear
No_Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Report ID, data= [ 0x25 ] 37
Item(Local ): Usage, data= [ 0x00 ] 0
Unassigned
Item(Main ): Input, data= [ 0x22 ] 34
Data Variable Absolute No_Wrap Linear
No_Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Global): Report Size, data= [ 0x08 ] 8
Item(Global): Report Count, data= [ 0x04 ] 4
Item(Global): Report ID, data= [ 0x26 ] 38
Item(Local ): Usage, data= [ 0x00 ] 0
Unassigned
Item(Main ): Input, data= [ 0x22 ] 34
Data Variable Absolute No_Wrap Linear
No_Preferred_State No_Null_Position Non_Volatile Bitfield
Item(Main ): End Collection, data=none
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 10
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
** UNRECOGNIZED: 09 21 11 01 00 01 22 2a 00
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)
The interesting thing is there are two interfaces, the first is the HID which includes the IR receiver, the second interface also has an interrupt IN endpoint. What could this be? Well, examining the ioref dump gives us a good hint.
+-o USB1@1D <class IOPCIDevice, registered, matched, active, busy 0, retain count 10>
| +-o AppleUSBUHCI <class AppleUSBUHCI, !registered, !matched, active, busy 0, retain count 10>
| |
| +-o UHCI Root Hub Simulation@1D <class IOUSBRootHubDevice, registered, matched, active, busy 0, retain count 13>
| | +-o AppleUSBHub <class AppleUSBHub, !registered, !matched, active, busy 0, retain count 4>
| | +-o IOUSBInterface@0 <class IOUSBInterface, !registered, !matched, active, busy 0, retain count 6>
| | +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
| +-o Hub in Apple Extended USB Keyboard@1d100000 <class IOUSBDevice, !registered, !matched, active, busy 0, retain count 6>
| |
| +-o IR Receiver@1d200000 <class IOUSBDevice, registered, matched, active, busy 0, retain count 10>
| +-o IOUSBCompositeDriver <class IOUSBCompositeDriver, !registered, !matched, active, busy 0, retain count 4>
| |
| +-o IOUSBInterface@0 <class IOUSBInterface, registered, matched, active, busy 0, retain count 7>
| | +-o AppleIRController <class AppleIRController, registered, matched, active, busy 0, retain count 9>
| | | +-o IOHIDInterface <class IOHIDInterface, registered, matched, active, busy 0, retain count 6>
| | | | +-o IOHIDEventDriver <class IOHIDEventDriver, registered, matched, active, busy 0, retain count 8>
| | | | +-o IOHIDConsumer <class IOHIDConsumer, registered, matched, active, busy 0, retain count 8>
| | | | | +-o IOHIDSystem <class IOHIDSystem, registered, matched, active, busy 0, retain count 11>
| | | | | | +-o IOHIDUserClient <class IOHIDUserClient, !registered, !matched, active, busy 0, retain count 5>
| | | | | | +-o IOHIDParamUserClient <class IOHIDParamUserClient, !registered, !matched, active, busy 0, retain count 5>
| | | | | +-o IOBSDConsole <class IOBSDConsole, !registered, !matched, active, busy 0, retain count 5>
| | | | +-o IOHIDSystem <class IOHIDSystem, registered, matched, active, busy 0, retain count 10>
| | | | +-o IOHIDUserClient <class IOHIDUserClient, !registered, !matched, active, busy 0, retain count 5>
| | | | +-o IOHIDParamUserClient <class IOHIDParamUserClient, !registered, !matched, active, busy 0, retain count 5>
| | | +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
| | | +-o IOHIDLibUserClient <class IOHIDLibUserClient, !registered, !matched, active, busy 0, retain count 6>
| | +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
| |
| +-o IOUSBInterface@1 <class IOUSBInterface, registered, matched, active, busy 0, retain count 7>
| | +-o AppleSMC <class AppleSMC, registered, matched, active, busy 0, retain count 4>
| | +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
| |
| +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
"IOUSBInterface@0" uses endpoint 2 IN, and "IOUSBInterface@1" uses endpoint 1 IN. Well what do you know, that's where AppleSMC is hiding.
+-o IOUSBInterface@1 <class IOUSBInterface, registered, matched, active, busy 0, retain count 7>
| | {
| | "IOUserClientClass" = "IOUSBInterfaceUserClient"
| | "idProduct" = 33345
| | "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
| | "iInterface" = 0
| | "bAlternateSetting" = 0
| | "bConfigurationValue" = 1
| | "bInterfaceProtocol" = 0
| | "bInterfaceNumber" = 1
| | "bInterfaceSubClass" = 0
| | "idVendor" = 1452
| | "bInterfaceClass" = 255
| | "locationID" = 488636416
| | "bNumEndpoints" = 1
| | "bcdDevice" = 578
| | }
| |
| +-o AppleSMC <class AppleSMC, registered, matched, active, busy 0, retain count 4>
| | {
| | "idProduct" = 33345
| | "bConfigurationValue" = 1
| | "CFBundleIdentifier" = "com.apple.driver.AppleSMC"
| | "IOClass" = "AppleSMC"
| | "IOProbeScore" = 20000
| | "IOMatchCategory" = "IODefaultMatchCategory"
| | "bInterfaceNumber" = 1
| | "IOUserClientClass" = "AppleSMCClient"
| | "idVendor" = 1452
| | "IOProviderClass" = "IOUSBInterface"
| | }
| |
| +-o IOUSBUserClientInit <class IOUSBUserClientInit, !registered, !matched, active, busy 0, retain count 4>
| {
| "IOMatchCategory" = "IOUSBUserClientInit"
| "IOProbeScore" = 9000
| "IOClass" = "IOUSBUserClientInit"
| "IOProviderClass" = "IOUSBInterface"
| "CFBundleIdentifier" = "com.apple.iokit.IOUSBUserClient"
| "IOProviderMergeProperties" = {"IOUserClientClass"="IOUSBInterfaceUserClient","IOCFPlugInTypes"={"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}}
| }