Reputation: 11569
I'm trying to learn about USB protocol by analyzing Wireshark output of sniffing on my keyboard. For example, consider this frame:
Frame 29335: 72 bytes on wire (576 bits), 72 bytes captured (576 bits) on interface usbmon1, id 0
Interface id: 0 (usbmon1)
Encapsulation type: USB packets with Linux header and padding (115)
Arrival Time: Jan 4, 2022 17:44:50.003878000 CET
[Time shift for this packet: 0.000000000 seconds]
Epoch Time: 1641314690.003878000 seconds
[Time delta from previous captured frame: 0.205081000 seconds]
[Time delta from previous displayed frame: 3.343982000 seconds]
[Time since reference or first frame: 342.817999000 seconds]
Frame Number: 29335
Frame Length: 72 bytes (576 bits)
Capture Length: 72 bytes (576 bits)
[Frame is marked: False]
[Frame is ignored: False]
[Protocols in frame: usb]
USB URB
[Source: 1.5.1]
[Destination: host]
URB id: 0xffff8cbe330fba80
URB type: URB_COMPLETE ('C')
URB transfer type: URB_INTERRUPT (0x01)
Endpoint: 0x81, Direction: IN
Device: 5
URB bus id: 1
Device setup request: not relevant ('-')
Data: present (0)
URB sec: 1641314690
URB usec: 3878
URB status: Success (0)
URB length [bytes]: 8
Data length [bytes]: 8
[Request in: 29167]
[Time from request: 3.343946000 seconds]
[bInterfaceClass: Unknown (0xffff)]
Unused Setup Header
Interval: 16
Start frame: 0
Copy of Transfer Flags: 0x00000204, No transfer DMA map, Dir IN
Number of ISO descriptors: 0
Leftover Capture Data: 0000290000000000
Here's related lsusb output:
> sudo lsusb -s 001:005 -vvvvv
Bus 001 Device 005: ID 046d:c312 Logitech, Inc. DeLuxe 250 Keyboard
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x046d Logitech, Inc.
idProduct 0xc312 DeLuxe 250 Keyboard
bcdDevice 1.01
iManufacturer 1 LITEON Technology
iProduct 2 USB Multimedia Keyboard
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0022
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 70mA
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 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.10
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 24
can't get debug descriptor: Resource temporarily unavailable
Device Status: 0x0000
(Bus Powered)
The "29" differs based on the key I press. How can I map it back to a specific key? Is there some more context needed in order to interpret this frame?
Upvotes: 1
Views: 549
Reputation: 2884
The USB payload represents scancodes (see here: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html). The interpretation requires a bit more context.
The eXtensible Host Controller Interface (xHCI) defines a register level interface to interact with USB on modern systems (https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf). As I read here and there, most computers (including ARM computers) support xHCI for their USB host controller. In general, it will be in the form of a PCI-Express device called an xHC (in Intel's terminology).
PCI devices are MMIO and DMA. To interact with PCI devices, you write in RAM at conventionnal positions specified by ACPI tables (the MCFG, more specifically). When you write in RAM at these conventionnal positions, it will write to the registers of the PCI device instead which allows to control the device. PCI devices also read/write in RAM directly.
The xHC has interrupt transfer rings. The software (the OS) will put TDs at an appropriate depth on the interrupt transfer ring of the keyboard interrupt endpoint. As stated in the xHCI specification (linked above):
If multiple Interrupt TDs are posted to an Interrupt endpoint Transfer Ring, the xHC should consume no more than one TD per ESIT.
Basically, you program ESIT with a value. The ESIT value tells the xHC to not consume a TD too often. This will allow to trigger transfers at specific intervals in time. The right interval of time is specified in the USB descriptor returned by the USB device.
Every time a transfer occurs, the software (OS) reads the values and determines if there was any change in the keys pressed. The keyboards are Human Interface Devices (HID) which are specified as part of the USB standard (https://wiki.osdev.org/USB_Human_Interface_Devices).
When asked to transfer data, the USB keyboards (HID) return 8 bytes (as seen in your example). As stated on osdev.org:
This report must be requested by the software using interrupt transfers once every interval milliseconds, and the interval is defined in the interrupt IN descriptor of the USB keyboard. The USB keyboard report may be up to 8 bytes in size, although not all these bytes are used and it's OK to implement a proper implementation using only the first three or four bytes (and this is how I do it.) Just for completion's sake, however, I will describe the full report mechanism of the keyboard. Notice that the report structure defined below applies to the boot protocol only.
Maybe read the HID article of osdev.org and that will bring you a long way. The USB payload can thus be interpreted to be the data that has been sent by the keyboard after an interval in milliseconds specified in the USB descriptor of the keyboard has passed. Every time new data comes in, the new data is the key(s) pressed at that moment on the keyboard when it was queried by the xHC every ESIT or so.
Upvotes: 1