experiments
Yesterday

NetBSD и NVIDIA Optimus

Чтобы задать уровень выдаваемой дичи сразу на весь 2026 год вперед, я написал эту статью.

Да, это NetBSD на ноутбуке. Да опять. А как вы проводите новогодние праздники?

NetBSD

В самом начале нашего ролика про OpenBSD было сказано, что «шанс встретить живого пользователя этой системы примерно равен шансу встретиться с космонавтом».

Так вот сообщаю:

NetBSD — еще более редкая система, особенно для РФ и СНГ.

Поэтому шанс встретиться с ее живыми пользователями исчезающе мал, даже в Европе и США.

И скорее всего для такой встречи придется лезть в какой-нибудь «заповедник науки», вроде CERN и отвлекать местных PhD от андронного коллайдера.

Стоит сказать, что для такой редкости есть вполне объективные причины:

Если FreeBSD это «daily use», OpenBSD — безопасность, ну а NetBSD.. NetBSD это чистый треш-угар «Research and Development» (R&D).

Как нетрудно догадаться, вопросами R&D в области операционных систем занимаются исчезающе мало людей во всем мире, поэтому даже о существовании такой системы откровенно мало кому известно.

Особенно дальше статьи википедии.

Камон гайз, если даже линукс считается «игрушкой для задротов» — о чем вообще можно говорить?

NetBSD это Беркли и CERN, это редкое и нестандартное железо, это портирование на тостеры в качестве хобби, это фурри и содоми.. хотя нет, последнее это скорее про Arch Linux.

Короче, это система точно не для слабых умом и обиженных жизнью.

NetBSD на ноутбуке

Да, я действительно использую NetBSD на «голом железе», без виртуализации, причем на ноутбуках.

Все описанное в статье — для Lenovo Z580, но NetBSD также вполне нормально работает у меня и на Lenovo T440.

Для информации, вот тут описан весь мой ИТ-зоопарк, с описанием железа и конкретных настроек для каждого зверя.

С учетом редкости NetBSD, думаю несложно догадаться, чего стоит такое использование:

приходится натуральным рыть исходники ядра, списки рассылок и багтеркеры, чтобы найти решение той или иной проблемы

Короч это непросто, временами очень.

Одной такой проблемой реального использования NetBSD на практике, которую я долгое время не мог решить, была дискретная видеокарта от NVIDIA с технологией Optimus.

Точнее один ее подлый прикол:

даже без подключенного драйвера, запускающего процесс инициализации, карта считала себя активированной, с чего сильно грела ноутбук и сжирала батарею.

Собственно вот так выглядел репорт из рассылки NetBSD (см. ниже), с описанием сего скотского поведения:

Работать в дороге без подключения к розетке на таком ноутбуке было крайне проблематично.

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

Разумеется оставался вариант с аппаратным отключением дискретной видеокарты в BIOS, но мне быстро надоело туда лазить при переключении в Windows и Linux, где с программным отключением все было хорошо.

Стоит также заметить, что хотя в игры я не особо играю, дискретная карта все же нужна на линуксе и в Windows (да, у меня много всякого), когда например гоняю новинки демосцены с использованием аппаратного ускорения или еще какую дичь, связанную с 3D-графикой.

Так что держать дискретную карту всегда отключенной в BIOS не очень подходило.

NetBSD официально не поддерживается NVIDIA, поэтому нормальных драйверов для их видеокарт тут нет.

Зато есть Nouveau — открытая версия, которая по качеству постепенно догоняет закрытую версию, причем с активным участием самой NVIDIA в этом процессе.

К сожалению загрузка модуля Nouveau никак не влияет на возможность программного отключения карты, поскольку обвязки для управления переключением видеокарт в NetBSD нет.

Так что вне зависимости от того какая именно карта используется в Xorg, дискретная видеокарта NVIDIA будет продолжать считать себя включенной и соответственно греться и жрать батарею.

Кровавый ресерч

Для начала был найден способ отключать дискретную карту на дружественной FreeBSD, если в кратце:

с помощью специального модуля ядра acpi_call, портированного из Linux, делается прямой вызов метода подсистемы ACPI, отвечающего за установку флага отключения карты.

Поскольку у разных карт разные флаги отвечают за отключение, вызов делается путем перебора. Таким образом удалось узнать строку отключения, применимую именно к моей видеокарте:

\\_SB.PCI0.PEG0.PEGP._OFF

У вашей модели ноутбука эта строка скорее всего будет другой.

Затем в почтовых списках рассылки, посвященной NetBSD, было обнаружено сообщение с исходным кодом модуля ядра NetBSD, реализующего аналогичную логику:

вызов метода ACPI и передача строки отключения

Вот этот код, в немного переделанном виде:

#include <sys/param.h>
#include <sys/module.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>

// регистрация модуля ядра NetBSD, с указанием стартового метода
MODULE(MODULE_CLASS_DRIVER, fuck_nvidia, NULL);

#define NVIDIA_ON "\\_SB.PCI0.PEG0.PEGP._ON"
#define NVIDIA_OFF "\\_SB.PCI0.PEG0.PEGP._OFF"

/*
 *  Turns nVidia card off upon load and on upon unload.
    Общий метод, вызываемый при всем жизненном цикле модуля: 
    инициализация, запрос состояния, выгрузка
 */
static int
fuck_nvidia_modcmd(modcmd_t cmd, void *aux)
{
        ACPI_INTEGER val;
        ACPI_STATUS rv;

        switch (cmd) {
         // был запрос на инициализацию модуля
         case MODULE_CMD_INIT:
    		    printf("NVIDIA init\n");
    		    // вызов метода ACPI для отключения дискретной карты
                rv = acpi_eval_integer(NULL, NVIDIA_OFF, &val);
                // обработка ошибок
                if (ACPI_FAILURE(rv))
                        printf("Failed to evaluate '%s': %s\n",
                            NVIDIA_OFF, AcpiFormatException(rv));
		        else
	                    printf("NVIDIA disabled\n");		
                break;
         // запрос на выгрузку модуля
         case MODULE_CMD_FINI:
    		    printf("NVIDIA finish\n");
    		    // вызов метода ACPI для включения дискретной карты
                rv = acpi_eval_integer(NULL, NVIDIA_ON, &val);

                if (ACPI_FAILURE(rv))
                        (void)printf("Failed to evaluate '%s': %s\n",
                            NVIDIA_OFF, AcpiFormatException(rv));

                break;

         default:
                return ENOTTY;
        }
        return 0;
}

По сути я лишь вставил чуть больше логирования, чтобы легче отслеживать работу логики отключения/включения. Однако простота кода — обманчива и главная проблема оказалась далеко не в нем одном.

Ядерный угар

Когда дело доходит до патчей ядра операционной системы, объем и сложность исходного кода уходят на второй план, поскольку ядерный код обычно с длинной бородой историей правок и подробными комментариями по логике работы, это мягко говоря не «сто миллионов корпоративного кода на Java с нуля за три месяца».

Зато место правки, логика активации и проверка работоспособности становятся крайне важны.

Так оказалось и на этот раз, автор оригинального патча либо забыл о некоторых нюансах, либо его код успел устареть с 2012 года.

Короче сразу и правильно оно не заработало, пришлось применять инженерные навыки.

Начну с уникальной мысли:

Для того чтобы собрать модуль ядра, нужны исходники этого ядра

Для NetBSD, они выкладываются в виде архивов, привязанных к релизной версии NetBSD, для текущей (на момент написания статьи) версии 10.1 это выглядит так:

Исходники системы и ядра находятся в разных архивах, нужный нам файл называется syssrc.tgz, скачиваем и распаковываем в /usr/src:

cd /usr/src
wget https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/source/sets/syssrc.tgz
tar -xzf syssrc.tgz -C /

Должна появиться папка sys:

Использование готовых архивов это не единственный вариант, если собираетесь эксплуатировать NetBSD более-менее долго — будет разумнее сразу скачать исходники из репозитория CVS и оттуда же потом забирать обновления.

Дальше есть два стула варианта:

  • собрать кастомное ядро целиком
  • собрать только модуль для текущего релизного ядра

Разница в том, что NetBSD (как и другие BSD-системы) регулярно обновляется и временами, как часть бинарных обновлений, приходят новые сборки ядра.

Собранный для релизной версии модуль может перестать работать на обновленной версии.

Так что показываю на всякий случай оба варианта.

Кастомное ядро

Для сборки кастомной версии ядра, переходим в каталог:

/usr/src/sys/arch/amd64/conf

и копируем файл с конфигурацией ядра по-умолчанию (GENERIC):

cp GENERIC ALEXS

Затем запускаем скрипт конфигурации, который подготовит каталог сборки:

config ALEXS

Так это выглядит:

Переходим в каталог сборки ../compile/ALEXS:

Запускаем сборку:

make depend
make

Готовое ядро это файл netbsd, а его установка это простое копирование в корневой каталог:

cp netbsd /

Разумеется старое релизное ядро стоит сохранить, оно пригодится если что-то пойдет не так:

cp /netbsd /netbsd.old

В действии:

Теперь рассказываю про сам модуль для отключения дискретной видеокарты.

Модуль fuck_nvidia

Первым делом создаем каталог для нового модуля ядра:

cd /usr/src/sys/modules
mkdir fuck_nvidia

Создаем файл сборки Makefile с вот таким содержимым:

.include "../Makefile.inc"
.PATH:  ${S}/dev/acpi
KMOD=   fuck_nvidia
SRCS=   fuck_nvidia.c
WARNS=  4
.include <bsd.kmodule.mk>

Теперь переходим в каталог sys/dev/acpi и создаем файл с исходником модуля:

cd /usr/src/sys/dev/acpi
nano fuck_nvidia.c

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

Переходим обратно в каталог sys/modules и запускаем сборку модуля:

cd /usr/src/sys/modules/fuck_nvidia
make

В результате сборки должен появиться файл .kmod — наш новый модуль ядра:

Модуль можно сразу попробовать загрузить:

modload ./fuck_nvidia.kmod

Если все сделано правильно — система зависнет произойдет отключение дискретной карты, что будет сразу слышно по уменьшению шума вентиляторов.

Посмотреть список загруженных модулей можно командой modstat, для выгрузки модуля:

modunload fuck_nvidia

Автоматическая загрузка

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

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

cd /usr/src/sys/modules/fuck_nvidia
make install

В действии:

После чего можно будет загружать модуль по его имени, без пути к файлу .kmod:

modload fuck_nvidia

Следующим шагом включаем автозагрузку модуля, для чего надо активировать.. сервис загрузки модулей ядра:

echo modules=YES >>/etc/rc.conf

Сам список загружаемых модулей находится в отдельном конфигурационном файле modules.conf, добавляем в него наш модуль:

echo fuck_nvidia >>/etc/modules.conf

Перезагружаем систему, теперь при запуске будет загружаться наш модуль и отключать к х#ям этот NVIDIA Optimus.

Ура товарищи!

Встроенный модуль

В оригинальном сообщении из почтовой рассылки NetBSD был вот такой шаг:

Edit file "src/sys/dev/acpi/files.acpi" and put the following there:

        device  nvidia
        file    dev/acpi/nvidia.c               acpi

Этим шагом вы получите так называемый «build-in» — встроенный модуль ядра, команды modload/modunload работать будут, но вместо физической выгрузки будет происходить лишь активация/деактивация модуля, с вызовом соответствующих методов.

К сожалению мне не удалось добиться стабильной работы отключения карты в таком режиме, скорее всего из-за очередности загрузки — модуль отправлял команду на отключение через подсистему ACPI слишком рано, до того как сама ACPI полностью инициализировалась.

Вторая возможная причина: конфликт с модулем Nouveau, который активирует карту при запуске системы.

Так что оставляю исследование этого вопроса потомкам на ваше усмотрение.

Дополнительно

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

Ниже несколько интересных статей по этой сложной теме, которые позволят лучше разобраться в предмете:

Финальный «hero screen» c отключенной NVIDIA Optimus: