Содержание статьи
С железом нередко бывает так, что одного драйвера в пространстве ядра ОС для его работы недостаточно. Нужна также прошивка (firmware), которая загружается в само устройство. Точный формат и назначения прошивки зачастую известны только производителю: иногда это программа для микроконтроллера или FPGA, а иногда просто набор данных. Пользователю это не важно, главное, что устройство не работает, если ОС не загрузит в него прошивку.
В свободных операционных системах прошивки нередко вызывают споры. Многие из них распространяются под несвободными лицензиями и без исходного кода. Авторы OpenBSD и ряда дистрибутивов GNU/Linux считают это проблемой и со свободой, и с безопасностью и принципиально не включают такие прошивки в установочный образ.
Если у тебя есть устройство, которое требует прошивки, и тебе нужно, чтобы оно работало, вопрос лицензии прошивки становится чисто академическим — от необходимости иметь ее в системе и загружать ты никуда не уйдешь.
Полный набор из linux-firmware занимает более 500 Мбайт в распакованном виде. При этом каждой отдельно взятой системе требуется только небольшая часть этих файлов, остальное — мертвый груз.
Даже в современном мире с дисками на несколько терабайт еще много случаев, когда размер имеет значение: встраиваемые системы, образы для загрузки через PXE и подобное. Хорошо, если о board support package позаботился кто-то другой, но это не всегда так.
Если ты точно знаешь полный список нужного железа, можно извлечь файлы вручную. Впрочем, даже в этом случае найти нужные файлы может быть непросто — linux-firmware представляет собой не очень структурированную кучу файлов, и списка соответствия файлов именам модулей ядра там нет. А если ты хочешь дать пользователям возможность легко собрать свой образ, тут и вовсе нет выбора — нужно автоматическое решение.
В этой статье я расскажу о своем способе автоматической сборки. Он неидеален, но автоматизирует большую часть работы, что уже неплохо. Писать скрипт будем на Python 3.
INFO
Примеры кода в статье упрощенные. Готовый и работающий скрипт ты можешь найти на GitHub.
К примеру, можно им просмотреть список прошивок для включенных в .config
драйверов сетевых карт Realtek.
$ make menuconfig
$ make prepare
$ list-required-firmware.py -s drivers/net/ethernet/realtek/
rtl_nic/rtl8168d-1.fw
rtl_nic/rtl8168d-2.fw
rtl_nic/rtl8168e-1.fw
...
Основы
В ядре Linux нет глобального списка прошивок и кода для их загрузки. Каждый модуль загружает свои прошивки с помощью функций из семейства request_firmware
. Логично, если учесть, что процедура загрузки прошивки у каждого устройства разная. Соответственно, информация о нужных прошивках разбросана по множеству отдельных файлов с исходным кодом.
На первый взгляд, некоторую надежду дает опция сборки FIRMWARE_IN_KERNEL
. Увы, на деле она встраивает в файл с ядром только файлы, которые ты явно укажешь в EXTRA_FIRMWARE
. Так что файлы все равно сначала придется найти.
Поиск по вызовам request_firmware()
тоже не очень перспективен. Некоторые модули поддерживают несколько разных прошивок, да и имя файла часто хранится в переменной. В качестве примера можно посмотреть на фрагмент кода из драйвера сетевой карты Intel e100.
К счастью для нас, модули должны указывать нужные им файлы прошивок с помощью макроса MODULE_FIRMWARE()
. Пример можно найти в e100. Этот макрос определен в файле include/linux/module.h
.
Именно из вывода этого макроса берется информация о прошивках, которую можно увидеть в выводе утилиты modinfo
.
$ sudo modinfo e100 | grep firmware
firmware: e100/d102e_ucode.bin
firmware: e100/d101s_ucode.bin
firmware: e100/d101m_ucode.bin
В ряде случаев можно было бы обойтись одной modinfo
. Если у тебя есть собранное ядро и возможность его загрузить, ты можешь просмотреть вывод modinfo
для каждого нужного модуля. Это не всегда удобно или вообще возможно, так что мы продолжим искать решение, для которого понадобится только исходный код ядра.
Здесь и далее будем считать, что все ненужные модули отключены в конфиге сборки ядра (Kconfig). Если мы собираем образ для конкретной системы или ограниченного набора систем, это вполне логичное предположение.
Ищем исходники модулей
Чтобы собрать список имен файлов прошивок, мы сначала составим список всех файлов исходного кода ядра, где они используются. Затем мы прогоним эти файлы через препроцессор из GCC, чтобы раскрыть все макросы, и извлечем собственно имена нужных файлов.
Находим все включенные в конфиге модули
Это самая простая часть. Конфиг сборки ядра имеет простой формат «ключ — значение» вроде CONFIG_IWLWIFI=m
. Значение может быть n
(не собирать), y
(встроить в ядро) или m
(собрать в виде модуля).
Нас интересуют только ключи, а какое значение там, y
или m
, нам не важно. Поэтому мы можем выгрести нужные строки регулярным выражением (.*)=(?:y|m)
. В модуле re
из Python синтаксис (?:...)
используется для незахватывающих групп (non-capturing group), так что захвачена будет только часть в скобках из (.*)=
.
def load_config(path):
with open(path, 'r') as f:
config = f.read()
targets = re.findall(r'(.*)=(?:y|m)', config)
return targets
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»