unix
January 27

История одного патча

Еще один пример решения сложных проблем с оборудованием в альтернативных ОС — на этот раз в Linux.

Чтобы у вас не было иллюзии будто «оно все работает само».

Божественные Вайнона Райдер и Натали Портман, работы нейросети. Ну и пропатченное ядро.

Еще одна интересная история про войну с оборудованием — про FreeBSD и тачпад находится тут.

Вводная

Я очень давно использую самые разнообразные версии и вариации Linux и UNIX-систем на ноутбуках, стараясь решать все найденные проблемы по мере сил.

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

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

Описываемая проблема — как раз из последних.

Проблема

В кратце проблема заключалась в следующем:

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

Естественно в Windows все работало правильно, в любых режимах засыпания.

Ноутбук редкий, ноутбук старый (очень), от вендора, который никогда не любил альтернативные ОС и тем более в страшном сне не мог представить, что одна из его топовых моделей вместо подсчета прибылей и показа порно для очередного успешного менеджера, стала бы использоваться для компиляции ядра из исходников и прочих гиковских непотребств.

Никакая отладка ядра и никакие отладочные сообщения не помогли, что вообщем-то и неудивительно:

Работа с ACPI — традиционно самая гемморойная область ядра Linux, а процессы засыпания и возвращения к работе — невероятно сложны и нестабильны по своей сути.

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

Процесс отлова ошибок связанных с ACPI усложнен тем, что такие ошибки чаще всего «плавающие» — могут появиться не через один цикл «suspend-resume», а скажем.. через десять.

Правда ведь отладка это весело?

Решение

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

И помог мне в этом случайный коммент одного неизвестного китайца:

I have few reputation so I can't tell others about solution in their questions, so I will post it here.
Solution : build kernel with this PATCH
Description : I tracked the issue ,I discovered that all was working good until the 4.19.86 kernel.
So I checked DIFF and After many tries maybe 50 or more.
I found the cause : It was (if statement was added in 4.19.86 COMMIT) ,particularly checking if spaceid == 0 [ACPI_ADR_SPACE_SYSTEM_MEMORY]. Commit Link

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

Еще речь шла про совсем уж древнюю версию ядра (4.19, релиз в 2018м году) а само сообщение датировалось 2022м годом.

Но вы ведь и не думали, что все будет настолько просто, верно?

Решение

Посмотрим код виновника торжества, файл drivers/acpi/acpica/evregion.c, конкетно нас интересует метод acpi_ev_execute_reg_methods, а еще точнее — вот этот замечательный комментарий:

/*
* These address spaces do not need a call to _REG, since the ACPI
* specification defines them as: "must always be accessible". Since
* they never change state (never become unavailable), no need to ever
* call _REG on them. Also, a data_table is not a "real" address space,
* so do not call _REG. September 2018.
*/

Обратите внимание на дату — 2018й год, год выпуска версии ядра 4.19, в которой и проявилась данная проблема.

По сути самого комментария и исходя из логики ниже:

if ((space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) ||
	    (space_id == ACPI_ADR_SPACE_SYSTEM_IO) ||
	    (space_id == ACPI_ADR_SPACE_DATA_TABLE)) {
		return_VOID;
}

получается, один из коммитеров ядра в 2018м году хотел «сделать как лучше».

Думая, будто строгое соотвествие спецификации ACPI в данном конкретном случае бывает в 100% случаев — он вставил заглушку, убирающую вызов _REG метода для вроде как системных частей ACPI-прошивки.

Чем и похерил восстановление статуса батареи при пробуждении ноутбука.

Благими намерениями выстелена дорога в ад (ц)

Исправление

Все что нужно сделать для исправления ситуации, это убрать из условия проверки константу ACPI_ADR_SPACE_SYSTEM_MEMORY:

if (
        //    (space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) ||
	    (space_id == ACPI_ADR_SPACE_SYSTEM_IO) ||
	    (space_id == ACPI_ADR_SPACE_DATA_TABLE)) {
		return_VOID;
}

Ну и опционально добавить отладку, чтобы убедиться в правильности работы:

if (space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
		printk(KERN_DEBUG "PRO HEHE : Bypassing for battery is done");
}

Отладка была включена в текущем ядре а само сообщение можно наблюдать на стартовой картинке к статье.

Для того чтобы убедиться в правильности работы, достаточно усыпить ноутбук и подергать состояние батареи несколько раз:

PS

Такой патч никогда не примут в аппстрим, потому что он неправилен с точки зрения спецификации ACPI.

Собственно сама проблема с зависшей батареей появилась из-за того что некоторые вендоры класть хотели на стандарты и спецификации.

Но и сама ситуация и решение — (к великому сожалению) типовые, точно также решаются ныне и другие проблемы совместимости.

PPS

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

XanMod is a general-purpose Linux kernel distribution with custom settings and new features. Built to provide a stable, smooth and solid system experience.

The real-time version is recommended for critical runtime applications such as Linux gaming server / client for eSports, streaming, live productions and
ultra-low latency enthusiasts.

Подтверждаю — этот набор патчей действительно сильно ускоряет работу, что особенно заметно на некрожелезе времен молодости Брежнева, которое я регулярно использую для тренировки стойкости духа.