.. _matching_programs:

Matching BPF programs to a device
==================================

This tool supports multiple ways of matching a BPF program to a HID device:

Manual loading
--------------

Users can manually attach a HID-BPF program to a device:

.. code-block:: console

   $ sudo udev-hid-bpf add /sys/bus/hid/devices/0003:05F3:0405 trace_hid_events.bpf.o
   $ sudo udev-hid-bpf add /sys/bus/hid/devices/0003:05F3:0405 /path/to/my-hack.bpf.o

Multiple devices and object files can be provided by separating devices and BPF
object files with a literal ``-``. This gives us a convenient shortcut, useful
especially during development:

.. code-block:: console

   $ sudo udev-hid-bpf add - builddir/src/bpf/0010-Foo__Bar.bpf.o

By leaving the device list empty (``-`` is the first argument to ``add``),
``udev-hid-bpf`` will look up local devices based on the :ref:`metadata
<matching_programs_metadata>` compiled into the given BPF programs.

Note that the filename does not need to be a full path. The list of available
BPF programs can be shown with:

.. code-block:: console

   $ udev-hid-bpf list-bpf-programs

.. note:: If invoked from the git repository, this will show the BPF programs
          in the currently configured lookup directories. Use
          ``udev-hid-bpf list-bpf-programs --bpfdir builddir`` to list the
          programs in the builddir.

.. _matching_programs_metadata:

Metadata in the HID-BPF sources (modalias matches)
--------------------------------------------------

Each HID-BPF program can tell which devices it is supposed to be bound to.
If those metadata are given, udev will automatically bind the HID-BPF
program to the device on plug.

To do so, add metadata to your HID-BPF sources specifying the **bus**, the
**HID group**, and the **vendor** and **product** IDs. Here's an example of a
BPF program that matches multiple different devices and uses the ``#defines``
in ``hid_bpf_helpers.h``:

.. code-block:: c

   HID_BPF_CONFIG(
       /* A specific Logitech (0x046D) USB device on the generic HID group */
       HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x046D, 0x1234),

       /* A specific Yubikey (0x1040) USB device on the generic HID group */
       HID_DEVICE(0x3, HID_GROUP_GENERIC, 0x1040, 0x0407),

       /* Any logitech (0x046D) bluetooth device on the generic HID group */
       HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, 0x046D, HID_PID_ANY),

       /* Any i2c device */
       HID_DEVICE(BUS_I2C, HID_GROUP_ANY, HID_VID_ANY, HID_PID_ANY)
   );

As you can see, the arguments to the ``HID_DEVICE`` macro are

- the bus as either numerical value or one of ``BUS_USB``, ``BUS_BLUETOOTH``, ...
- the HID group as either numerical value or one of ``HID_GROUP_GENERIC``, ...
- the vendor ID in hexadecimal (see the ``lsusb`` output)
- the product ID in hexadecimal (see the ``lsusb`` output)

As used in the example above, ``BUS_ANY``, ``HID_GROUP_ANY``, ``HID_VID_ANY``
and ``HID_PID_ANY`` are wildcards.

For the curious, there is a page on :ref:`metadata` that explains how these metadata are
embedded in the resulting BPF object.

The easiest way to obtain the metadata is to use the
``udev-hid-bpf list-devices`` command:

.. code-block:: console

   $ udev-hid-bpf list-devices
   /sys/bus/hid/devices/0003:045E:07A5.0001
     - name:         Microsoft Microsoft® 2.4GHz Transceiver v9.0
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x045E, 0x07A5)
   /sys/bus/hid/devices/0003:045E:07A5.0002
     - name:         Microsoft Microsoft® 2.4GHz Transceiver v9.0
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x045E, 0x07A5)
   /sys/bus/hid/devices/0003:045E:07A5.0003
     - name:         Microsoft Microsoft® 2.4GHz Transceiver v9.0
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x045E, 0x07A5)
   /sys/bus/hid/devices/0003:046D:4088.0009
     - name:         Logitech ERGO K860
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, 0x046D, 0x046D)
   /sys/bus/hid/devices/0003:046D:C52B.0004
     - name:         Logitech USB Receiver
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x046D, 0xC52B)
   /sys/bus/hid/devices/0003:046D:C52B.0005
     - name:         Logitech USB Receiver
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x046D, 0xC52B)
   /sys/bus/hid/devices/0003:046D:C52B.0006
     - name:         Logitech USB Receiver
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x046D, 0xC52B)
   /sys/bus/hid/devices/0003:1050:0407.0007
     - name:         Yubico YubiKey OTP+FIDO+CCID
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x1050, 0x0407)
   /sys/bus/hid/devices/0003:1050:0407.0008
     - name:         Yubico YubiKey OTP+FIDO+CCID
     - device entry: HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x1050, 0x0407)

As shown above, many devices export multiple HID interfaces. See :ref:`run_time_probe` for details
on how to handle this situation.

Alternatively, the bus, group, vendor ID and product ID (``b``, ``g``, ``v``, ``p``)
can be extracted from the modalias of the device as provided by the kernel:

.. code-block:: console

   $ cat /sys/bus/hid/devices/0003:04D9:A09F.0009/modalias
   hid:b0003g0001v000004D9p0000A09F

   $ cat /sys/class/hidraw/hidraw0/device/modalias
   hid:b0003g0001v000004D9p0000A09F

Just strip out the ``hid:`` prefix, extract the bus, group, vid, pid and done.

Sharing the same BPF program for different devices
---------------------------------------------------

The metadata supports basic globbing features via the special values of ``BUS_ANY``,
``HID_GROUP_ANY``, ``HID_VID_ANY`` or ``HID_PID_ANY``.
Any device that matches all the other fields will thus match. For example
a metadata entry of ``HID_DEVICE(BUS_USB, HID_GROUP_ANY, HID_VID_ANY, HID_PID_ANY)``
will match any USB device.

.. _run_time_probe:

Run-time probe
--------------

Sometimes having just the vendor/product ID is not enough to know if a program needs to be loaded.
For example, one mouse I am doing tests with (``mouse_invert_y.bpf.c`` with
``HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, 0x04d9, 0xa09f)``) exports 3 HID interfaces,
but the BPF program only applies to one of those HID interfaces.

``udev-hid-bpf`` provides a similar functionality as the kernel with a ``probe`` function.
Before loading and attaching any BPF program to a given HID device, ``udev-hid-bpf`` executes the syscall ``probe`` in the ``.bpf.c`` file if there is any.

.. code-block:: c

  SEC("syscall")
  int probe(struct hid_bpf_probe_args *ctx)
  {
      /* zero if we want to bind, nonzero otherwise*/
      ctx->retval = 0;

      return 0;
  }

The arguments of this syscall are basically the unique id of the HID device, its report descriptor and its report descriptor size:


.. code-block:: c

  struct hid_bpf_probe_args {
    unsigned int hid;
    unsigned int rdesc_size;  /* number of valid bytes */
    unsigned char rdesc[4096]; /* the actual report descriptor */
    int retval;
  };

If the BPF program sets the ``ctx->retval`` to zero, the  BPF program is loaded for this device. A nonzero value (typically ``-EINVAL``)
prevents the BPF program from loading. See the
``mouse_invert_y.bpf.c`` program for an example of this
functionality or the :ref:`tutorial_probe` section of the :ref:`tutorial`.

Also note that ``probe`` is executed as a ``SEC("syscall")``, which means that the bpf function
``hid_bpf_hw_request()`` is available if you need to configure the device before customizing
it with HID-BPF.
