
Всё началось с тупой идеи.
У меня есть мышь Logitech MX Vertical, которая постоянно перемещается между моей домашней машиной, рабочим ноутбуком и другими устройствами. Однажды я задумался: у этой штуки есть флэш-память. Она обязана быть, иначе как мышь запоминает настройку DPI между подключениями? А можно ли в этой памяти хранить что-то ещё?
Ага, мне было скучно.
Я решил использовать мышь в качестве крошечного USB-накопителя. Так как она физически перемещается между компьютерами, то, строго говоря, способна и переносить между ними данные.
Связь мышей Logitech выполняется по протоколу HID++, построенному поверх стандартного USB HID. Он частично задокументирован Logitech, частично подвергнут реверс-инжинирингу опенсорс-сообществом. Я написал на Rust инструмент для составления списка всех свойств, раскрываемых мышью. Их оказалось 33.
HID++ 2.0 работает следующим образом: у каждого устройства есть таблица свойств. Каждый элемент привязывает стабильный ID свойства к индексу конкретного устройства. ID постоянен для всех устройств Logitech. У каждой модели свой индекс. То есть мы сначала спрашиваем устройство «где находится свойство 0x2201?», и оно отвечает «по индексу 0x12 в этой мыши». Затем мы используем этот индекс для всех последующих вызовов. Каждый вызов представляет собой короткий пакет: ID отчёта, индекс устройства, индекс свойства, ID функции и до трёх байт параметров. Ответ поступает в таком же виде.
Большинство свойств незадокументировано. Есть свойства с именами наподобие EnableHiddenFeatures и TemplateBytesNVS. Похожее, последнее — это именно то, что мне нужно: постоянное хранилище, индекс 0x1eb0. Мне не удалось найти никакой информации в документации, но предположу, что оно как-то связано с блобами конфигурации для макросов или шаблонов кнопок. К сожалению, ни на какие запросы мышь не отвечает, так что выяснить этого мне не удалось.
Довольно долго я исследовал 0x1c00 (PersistentRemappableAction). Шесть слотов. флаги чтения/записи и так далее. Оказалось, что IOHIDManager macOS без уведомлений блокирует длинный формат отчёта HID++, который нужен для записи в него. Операционная система просто отбрасывает пакеты, не выдавая никаких ошибок или объяснений. Я выяснил это, написав кучу зондирующего кода и долго наблюдая пустые ответы. Да, способы обойти IOHIDManager есть; например, можно напрямую общаться с USB-устройством через IOKit, но это уже другая история.
Затем я обратил своё внимание на регистр имени устройства. Он выглядел перспективно и принимал вызовы записи. Но мышь просто повторяла «MX Vertical», чтобы я ни отправлял.
Подходящим для моих задач оказался регистр DPI.
Вообще, это было довольно очевидно, ведь DPI нужно было где-то хранить. И оказалось, что регистр принимает любое значение u16, которое ему передашь. Похоже, регистр DPI не выполняет ни округления, ни валидации или, по крайней мере, не выбрасывает исключение в случае передачи ему очевидно недопустимого DPI. Можно записать 0x6869, отключить мышь, подключить её к другому компьютеру и считать значение.
И вывод всегда будет равен hi.
2 байта постоянного хранилища, находящегося в регистре DPI мыши Logitech.
Я задумался о том, почему подобными проектами вообще стоит заниматься. Их результат объективно бесполезен: 2 байта — это ничто. Можно хранить больше данных, меняя имя хоста.
Однако смысл заключался в исследовании. Я узнал, как работает HID++. Я узнал, как macOS управляет HID-устройствами на уровне ядра и где она проводит черту. Я узнал, что таблица свойств (feature table) и индекс свойств (feature index) — это разные понятия, и что обратный поиск IFeatureSet на этом устройстве очевидно поломан. Я узнал, что регистр имени устройства вежливо принимает операции записи, но полностью игнорирует их (вполне привычной опыт моей жизни).
Эти знания я получил не благодаря чтению документации. Документация по Logitech hidpp20 существует, но половина свойств в ней отсутствует, поэтому мне пришлось экспериментировать. Чтобы всё заработало, мне пришлось идти путём проб и ошибок. Но, вероятно, вам не стоит повторять это со своим BIOS.
В конечном итоге, дело было не в 2 байтах. Мне просто интересно было узнать, насколько далеко можно зайти.
Если вы тоже захотите хранить что-нибудь в своей мыши, то код выложен здесь. Он работает с любыми мышами Logitech, имеющими приёмник Unifying.