Хабрахабр

[Из песочницы] Пишем драйвер для ноутбука for fun and profit, или как закоммитить в ядро даже если ты дурак

С чего всё началось

Начнём с постановки проблемы. Дано: один ноутбук. Новый ноутбук, геймерский. С RGB-подсветкой. Вот такой примерно ноутбук:

image
Картинка взята с lenovo.com

Программа как раз этой подсветкой и управляет. Есть ещё программа к этому ноутбуку.

И лампочки чтоб светились, и чтоб цвета красивые мелькали. Одна только проблема – программа под Windows, а хочется чтоб в любимом линуксе всё работало. Простой ответ пришёл быстро – никак. Да вот только как это сделать, чтоб без реверс-инжиниринга и без написания своих драйверов? Ну что ж, пошли писать драйвер.

Шаг 1 – копаемся в коде

У нас есть три места, из которых можно увидеть как подсветка мигает. В порядке возрастающей сложности:

Большая и накачанная геймерская программа Lenovo Nerve Center – в которой есть функция настройки всей этой подсветки. 1.

Сочетание горячих клавиш Fn+Space – возможно. 2. его обрабатывает эта же программа.

BIOS. 3. Во время загрузки ноутбука подсветка тоже мелькает – но только красным, и только на секунду.

О ней речь и пойдёт. Забегая вперёд, скажу что пришлось попробовать все три, но продвинулся с каким-никаким успехом я только по первой.

Ну что ж, откроем папку с программой:

folder

Наш ли...? Сразу замечаем, что есть DLL с интересным названием – LedSettingsPlugin.dll. Давайте откроем в IDA Pro и посмотрим.

right-half

От программистов осталось много дебажной информации – воспользуемся же ей. Обратите внимание на правую половину экрана. Почему? Видим, что почему-то есть много строк, похожих на имена методов.

name-of-func

Как удобно! А это и есть имена методов. Для называния в IDA можно использовать хот-кей N, или просто щелчок правой кнопкой мыши по тому, что хотим обозвать. Давайте поназываем что можем своими именами, и посмотрим на список функций ещё раз.

setledstatusex

Похоже, что нам надо! Смотрим на функции… Y720LedSetHelper::SetLEDStatusEx. Конкретно интересует var_38, все метаморфозы которой IDA любезно обозначил за нас. Замечаем, что тут формируется что-то вроде строки и передаётся потом в некий CHidDevHelper::HidRequestsByPath.

Он идёт сразу после var_38 – в традициях ассемблера переменные хранятся в обратном порядке под RSP. Var_34 нам тоже интересен. Var_34 здесь просто название константы – -34h.

(Забегая вперёд, скажу что три здесь это значение яркости. Получаем, что начиная с var_38 программа кладёт ноль, потом стиль, цвет, число три и блок – часть клавиатуры, к которой этот цвет применится. Сделав это управляемым, мы получим драйвер ещё круче оригинала!)

Давайте же залезем в HidRequestsByPath и наконец узнаем, как оно отправляется.

devhelper

Оба в файле не прослеживаются… Зато очень хорошо прослеживаются в официальной документации Майкрософт — тут и <a href=«docs.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_getfeature>тут. Видим две функции, HidD_GetFeature и HidD_SetFeature.

Это дно, глубже копать не надо. На этом можно себя поздравить – мы добрались. В Linux такие функции есть – надо только вызвать их с теми же аргументами, и всё должно получиться… ведь правда?

Шаг 2 – запускаем прототип...?

Не совсем. Начнём с простого, и запустим lsusb. Так найдём клавиатуру, и к ней что-нибудь пошлём.

lsusb

здесь выглядит самым интересным. Integrated Technology Express, Inc. Отыскиваем подходящий… Это делается простым поиском по файлам /sys/class/hidraw/hidraw*/device/uevent. Будем пользоваться известным инструментом /dev/hidraw.

hidraw

Цифры совпадают – значит это устройство есть hidraw0. Вот тот. Бред какой-то. Но мы пытаемся послать данные, и ничего не получается! На этом этапе руки начинают опускаться… Может быть, не для простых смертных это, этот реверс-инжиниринг?

Попытаемся. Но продолжим. Идём обратно в Windows, есть одна идея. Если автор бы разбирался в этом всём, он бы выреверсил поиск нашего драйвера из того же Nerve Center.… Но у нас нет мозга.

Многое позволяет делать – нам интересно то, что он позволяет отрубать девайсы. Есть в Windows такая вещь – Диспетчер Устройств. Просто и бескомпромиссно.

Давайте отрубать девайсы по одному, пока состояние лампочек не прекратит меняться.

disable-device

Значит, если заглянуть в Hardware ID – увидим и то, как его зовут и с чем его едят. Этот отрубился.

our-device

Смотрим – это же он.

dmesg

Девайс, который мне уже несколько лет флудил в dmesg.

Лирическое отступление – давайте сделаем коммит

В поисках правильного девайса я расковырял свою установку почти до ядра. Ещё, не очень нравилось что мне эту dmesg-простыню показывало перед каждой блокировкой. Раз уж я здесь, почему бы не написать короткий коммит? Руки всё равно чешутся.

Здесь. Что нам нужно посмотреть – где девайсы на I2C HID, и где писать какие странности им присущи. Давайте сделаем его correct. Не мудрствуя лукаво, прислушаемся к ошибке – incorrect input length.

bad-input-size

По аналогии с уже существующими. Добавим новый quirk, назовём его пусть I2C_HID_QUIRK_BAD_INPUT_SIZE.

quirk

То, что мы сделали пока: Добавим ещё наше устройство в список quirkнутых.

Поиск в интернете по запросу “i2c hid linux kernel”. 1. Кликнули на четвёртый ответ на DuckDuckGo.

Написали три (!) слова на английском – BAD_INPUT_SIZE 2.

Прибавили один к BIT(4). 3. Получилось BIT(5).

Добавили одну чиселку в hid-ids.h – ID нашего устройства (на иллюстрации не показано, но там так же примерно). 4.

Давайте теперь программировать.

Видим строку — `ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8;`

А потом оно жалуется, что ret_size не равен тому что оно хочет.

Давайте если квирк задействован, делать зто же самое, только наоборот.

if-condition

Это непросто.) Отправляем патч в рассылку, не забыв потестить… (если честно, чтоб просто добавиться в рассылку мозга уже потребовалось больше.

applied

И всё.

Шаг 3 – драйвер!

Тут я вспомнил — ах да, надо же драйвер ещё написать. Решил это в ядро пока не коммитить (там вопросы начали подниматься, реально думать пришлось. Если кто из хабровчан может помочь проконсультировать, напишите мне – буду рад!)

(В начале bash был, но я передумал очень быстро.) Будем пользоваться известным инструментом /dev/hidraw. Открываем питон.

Для простого ввода-вывода их можно использовать как любой нормальный файл, и он будет работать. /dev/hidraw0, /dev/hidraw1 — как вообще работают эти файлы? А вот с GetFeature и SetFeature придётся повозиться…

Это специальный метод работы с необычными файлами вроде HID-устройств, терминалов и им подобных. StackOverflow (или кто-то ещё, не помню уже) подсказал, что для этого нужен некий ioctl.

Ему даёшь дескриптор открытого файла (кому интересно, что это такое, есть ссылка на Википедию), какое-то число и буфер – после чего он с этим буфером что-то делает, и возвращает обратно. Работает ioctl так. Приведу пример: 0xC0114806, или уже нам знакомый SetFeature. Я не утрирую, там просто очень многое от имплементации зависит. Оно же

(6 << 29) | (ord('H') << 8) | (0x06 << 0) | (0x11 << 16).

Почему для чтения, не очень понятно — но написано делать так, наверное так и надо. Первая 6 здесь значит, открыт файл будет для записи и чтения. Может и другие вещи означать, в зависимости от файла. Ord(‘H’) здесь же – сокращённо HID. Шестая команда с HID и есть SetFeature. В этом случае HID.
0x06 – сама команда. Последняя часть это длина буфера.

Он работает. Остаётся только вызвать ioctl с этими значениями — и на выходе получаем драйвер.

Послесловие

Надеюсь, читать было интересно. Даже полезно, быть может. Некоторое было опущено или сокращено – зоркий читатель, быть может, обнаружит в драйвере и считывание состояния, и какой-то второй SetFeature, про который в тексте упомянуто вообще не было. Разработка драйверов и ядро Линукса – большие штуки, и за одну байку их полностью не осилить. Статья скорей не про это, а про то, что сделать что-то небольшое и приятное, в опен-сурс или для себя, совершенно не сложно. Надо только найти недельку вечеров с чаем и желание покопаться в коде.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть