История одного патча
Еще один пример решения сложных проблем с оборудованием в альтернативных ОС — на этот раз в 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.
Подтверждаю — этот набор патчей действительно сильно ускоряет работу, что особенно заметно на некрожелезе времен молодости Брежнева, которое я регулярно использую для тренировки стойкости духа.