Как я пытался подключить GoPro Hero 13 к ноутбуку, а в итоге пропатчил KDE

Недавно супруга попросила помочь с переносом видеозаписей с её GoPro на мой ноутбук. Раньше, когда камеры определялись как обычные USB-накопители с файловой системой FAT, подобных затруднений не возникало, однако с современными MTP-устройствами всё бывает совсем иначе:

Разумеется, можно было воспользоваться утилитой Android File Transfer for Linux, но я предпочел разобраться в корне проблемы самостоятельно.

Анализ дескриптора устройства

Любое исследование USB-периферии логично начинать с изучения её дескриптора. Уточним идентификаторы устройства и выгрузим его конфигурацию:

$ lsusb|grep -i gopro
Bus 001 Device 045: ID 2672:0059 GoPro HERO13 Black
$ lsusb -d2672:0059 -vvv >descriptor.txt

Содержимое полученного файла (из-за внушительного объема скрыто под спойлером):

descriptor.txt
Bus 001 Device 008: ID 2672:0059 GoPro HERO13 Black
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.10
  bDeviceClass            0 [unknown]
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x2672 GoPro
  idProduct          0x0059 HERO13 Black
  bcdDevice            0.01
  iManufacturer           1 GoPro
  iProduct                2 HERO13 Black
  iSerial                 3 C3534250246817
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x007c
    bNumInterfaces          3
    bConfigurationValue     1
    iConfiguration          4 Generic Config
    bmAttributes         0xc0
      Self Powered
    MaxPower              100mA
    Interface Association:
      bLength                 8
      bDescriptorType        11
      bFirstInterface         0
      bInterfaceCount         2
      bFunctionClass          2 Communications
      bFunctionSubClass      13 [unknown]
      bFunctionProtocol       0 
      iFunction               8 CDC NCM
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass        2 Communications
      bInterfaceSubClass     13 [unknown]
      bInterfaceProtocol      0 
      iInterface              5 CDC Network Control Model (NCM)
      CDC Header:
        bcdCDC               1.10
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1 
      CDC Ethernet:
        iMacAddress                      6 0457474BB944
        bmEthernetStatistics    0x00000000
        wMaxSegmentSize               1514
        wNumberMCFilters            0x0000
        bNumberPowerFilters              0
      CDC NCM:
        bcdNcmVersion        1.00
        bmNetworkCapabilities 0x11
          crc mode
          packet filter
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0010  1x 16 bytes
        bInterval               9
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      1 
      iInterface              7 CDC Network Data
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       1
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      1 
      iInterface              7 CDC Network Data
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           3
      bInterfaceClass         6 Imaging
      bInterfaceSubClass      1 Still Image Capture
      bInterfaceProtocol      1 Picture Transfer Protocol (PIMA 15470)
      iInterface             10 MTP
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x001c  1x 28 bytes
        bInterval               6
Binary Object Store Descriptor:
  bLength                 5
  bDescriptorType        15
  wTotalLength       0x0016
  bNumDeviceCaps          2
  USB 2.0 Extension Device Capability:
    bLength                 7
    bDescriptorType        16
    bDevCapabilityType      2
    bmAttributes   0x0000010e
      BESL Link Power Management (LPM) Supported
    BESL value      256 us 
  SuperSpeed USB Device Capability:
    bLength                10
    bDescriptorType        16
    bDevCapabilityType      3
    bmAttributes         0x00
    wSpeedsSupported   0x000f
      Device can operate at Low Speed (1Mbps)
      Device can operate at Full Speed (12Mbps)
      Device can operate at High Speed (480Mbps)
      Device can operate at SuperSpeed (5Gbps)
    bFunctionalitySupport   1
      Lowest fully-functional device speed is Full Speed (12Mbps)
    bU1DevExitLat          10 micro seconds
    bU2DevExitLat         511 micro seconds
Device Status:     0x0001
  Self Powered

Краткие выводы:

  • Интерфейсы 0 и 1 относятся к протоколу CDC Network и не представляют интереса — они обеспечивают работу камеры как сетевого адаптера.

  • Интерфейс 2 использует PTP (в частности, расширение MTP) и работает через конечные точки 0x83 (Bulk IN), 0x02 (Bulk OUT) и 0x84 (Interrupt IN).

  • Наличие точки с прерыванием (Interrupt IN) говорит о том, что камера асинхронно оповещает клиента о событиях.

Стоит заметить, что адреса этих точек не фиксированы стандартом, а динамически определяются хостом при анализе конфигурации устройства.

Диагностика через Wireshark

Следующий шаг — перехват USB-трафика. Подготовим окружение, чтобы обойтись без прав суперпользователя:

$ sudo modprobe usbmon
$ sudo chgrp wireshark /dev/usbmon*
$ sudo chmod g+r /dev/usbmon*
$ wireshark

Запустим сниффинг на интерфейсе usbmon0 с фильтром по нашему устройству и нужным точкам, чтобы отсеять лишний «шум»:

usb.device_address==30 && usb.endpoint_address in {0x83, 0x02, 0x84}

Если всё настроено верно, мы увидим обмен пакетами. Однако для глубокого анализа нам необходим специализированный диссектор протокола, и здесь на помощь придет AI.

Создание диссектора

Я воспользовался помощью Claude, скормив ему техническую документацию. В качестве основы для тестирования использовал дамп трафика (предварительно установив tshark).

Examine the docs and implement an MTP over USB protocol dissector for Wireshark 4.2.2 in Lua. You’re free to use tshark for the testing purposes. I uploaded an example of MTP communication in pcapng format for your reference — but keep in mind that it also contains CDC Network class communication that should be ignored.

Готовый скрипт mtp.lua нужно разместить в директории плагинов Wireshark (обычно /usr/lib/x86_64-linux-gnu/wireshark/plugins/), после чего перезапустить программу:

Теперь, когда коммуникация стала «читаемой», стало проще искать аномалии.

Суть проблемы

Довольно быстро удалось заметить интересную деталь: при запросе списка доступных хранилищ (Storage) от хоста, камера в ответ присылает пустой перечень.

Выяснилось, что GoPro не готова отдавать данные моментально после подключения — ей требуется пара секунд на инициализацию. По протоколу устройство должно оповестить хост событием PTP_EC_StoreAdded, однако наш Linux-стек (в лице kiod) даже не ожидал подобных уведомлений — запрос на прерывание попросту отсутствовал.

О низкоуровневых USB-транзакциях

Вкратце: клиент (например, libusb) инициирует трансфер, хост-контроллер отправляет токен IN. Если устройство не готово, оно шлет NAK, и трансфер «зависает» до готовности. ОС эти детали абстрагирует, и увидеть их можно лишь через аппаратный анализатор.

Исправление KDE

Решение — научить kiod мониторить события PTP и реагировать на них. Сначала получаем исходный код компонента:

$ apt-file search kf5/kiod/kmtpd.so
kio-extras: /usr/lib/x86_64-linux-gnu/qt5/plugins/kf5/kiod/kmtpd.so
$ apt-get source kio-extras

В недрах kio-extras/mtp/ нашлись механизмы оповещения системы о добавлении файлов. Я создал класс MTPEventWorker, который в отдельном потоке опрашивает устройство и генерирует сигналы:

void MTPEventWorker::run()
{
    while (!m_stop) {
        LIBMTP_event_t event;
        uint32_t storage_id = 0;
        const int ret = LIBMTP_Read_Event(m_device, &event, &storage_id);
        if (ret != 0) break;
        switch (event) {
        case LIBMTP_EVENT_STORE_ADDED:
            Q_EMIT storageAdded(storage_id);
            break;
        case LIBMTP_EVENT_STORE_REMOVED:
            Q_EMIT storageRemoved(storage_id);
            break;
        }
    }
}

Интеграция в MTPDevice выглядит следующим образом:

m_eventThread = new QThread(this);
m_eventWorker = new MTPEventWorker(m_mtpdevice);
m_eventWorker->moveToThread(m_eventThread);
connect(m_eventThread, &QThread::started,  m_eventWorker, &MTPEventWorker::run);
connect(m_eventWorker, &MTPEventWorker::storageAdded,   this, &MTPDevice::onStorageAdded);
m_eventThread->start();

В слоте onStorageAdded мы просто обновляем список хранилищ и отправляем сигнал KDirNotify::emitFilesAdded, чтобы файловый менеджер увидел обновленный контент. После пересборки и установки пакета проблема исчезла окончательно.

Аналогичные устройства (например, некоторые Android-смартфоны) часто решают проблему иначе — физическим переподключением через изменение состояния линии (pull-up), что KDE уже умеет корректно обрабатывать.

Итог

Надеюсь, это описание пролило свет на внутреннюю кухню USB/MTP-коммуникации и методы решения подобных «багов инициализации». Полный текст патча доступен по этой ссылке.

 

Источник

Читайте также