HID Report Descriptor Macros

In the tutorial’s Modifying the HID Report Descriptor section the example code changed individual bytes in the report descriptor. In many cases, particular where a new device needs to be supported, a more readable approach is to replace the report descriptor wholesale. For this, udev-hid-bpf provides a series of helper macros, see the hid_report_helpers.h. source file.

These macros are styled in the output format of hid-recorder.

Below is an annotated extract of an example report descriptor:

static const __u8 fixed_rdesc_vendor[] = {
        UsagePage_Digitizers                 // <- set usage page to Digitizer
        Usage_Dig_Digitizer                  // <- set usage ID to Digitizer (page: Digitizer)
        CollectionApplication(               // <- Start an Application Collection
                // -- Byte 0 in report
                ReportId(0x15)
                Usage_Dig_Stylus             // <- set usage ID to Stylus (page: Digitizer)
                CollectionPhysical(
                        // -- Byte 1 in report
                        LogicalMinimum_i8(0)
                        LogicalMaximum_i8(1)
                        ReportSize(1)
                        Usage_Dig_TipSwitch
                        Usage_Dig_BarrelSwitch
                        Usage_Dig_SecondaryBarrelSwitch
                        ReportCount(3)
                        Input(Var|Abs)       // <- Input field, abs values
                        ReportCount(3)
                        Input(Const)         // <- Input field, const (i.e. padding)
                        // ... many more fields omitted ...
                )
                FixedSizeVendorReport(11)    // <- see "Enforcing the right HID Report size"
        )                                    // <- closes the Application Collection
};

This report descriptor can then be used to wholesale replace the device’s report descriptor:

SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(my_rdesc_fixup, struct hid_bpf_ctx *hctx)
{
      __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);

      if (!data)
              return 0; /* EPERM check */

      __builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
      return sizeof(fixed_rdesc_vendor);
}

This approach is particularly useful where the device sends all events in a HID Vendor Collection that would otherwise be ignored by the kernel. By overwriting the HID report descriptor with one that identifies the various components of the HID report it may be possible to make the device work without having to change the actual HID reports in the BPF program.

Enforcing the right HID Report size

However there is a drawback: the kernel requires that the returned HID Report Descriptor describes at least one HID Report that is the same size as the original HID Report Descriptor describes. In other words, if the original HID Report Descriptor describes a report of size 11 the fixed HID Report Descriptor must include at least one report that is of size 11.

This can easily be achieved with the FixedSizeVendorReport(len) helper macro as shown above. This macro will add one additional vendor-specific HID Report to the HID Report Descriptor with the given size. This HID Report will be ignored by the kernel but serves to guarantee our BPF is handled correctly.