unix
Today

Патчим DSDT по хардкору

Утро нового дня, как это часто бывает в нынешние тяжелые времена, вместо радости и надежды принесло лишь новое сообщение об ошибке:

_CRT value is absurd, ignored (256.1C)

С учетом, что при 250 градусах начинает плавиться текстолит, сообщение действительно было «absurd», но откуда оно взялось и с какой радости забивает собой весь буфер ядра автор все же решил выяснить.

Так выглядит описываемая проблема.

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

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

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

Этот случай не стал исключением.

HP Compaq nx7300 во всей своей корпоративно-квадратной красоте 2006 года.

Девайс

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

Это еще один древний винтажный HP Compaq, модели nx7300 из 2006 года, выданный в качестве донора запчастей к nx7400.

Железо примерно того же уровня:

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

ACPI, DSDT и скотство вендоров

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

Для начала процитирую вики Archlinux:

DSDT (Differentiated System Description Table) is a part of the ACPI specification. It supplies information about supported power events in a given system.

Врядли стало понятнее, даже если владеете английским на должном уровне, так что немного раскрою тему.

В современных компьютерах (в первую очередь ноутбуках) работает сложная логика по динамическому управлению питанием:

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

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

Когда нагрузка спадает, запускается обратный процесс:

понижается частота процессора, уменьшается потребление электроэнергии и чип охлаждается.

А вентиляторы постепенно затихают и ноутбук перестает шуметь.

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

За управление этими процессами отвечает подсистема ACPI, одной из частей которой является та самая DSDT, описывающая события по управлению питанием, используемые оборудованием.

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

Особенно в части поддержки открытых систем.

Наконец последний важный нюанс:

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

Именно этим интересным процессом мы сейчас и займемся.

Возможные последствия (нет).

Отказ от ответственности

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

Пожалуйста не надо пытаться повторять описанное на сколь-нибудь дорогом или нужном вам оборудовании.

С другой стороны «только храбрые сердцем попадают в Вальгаллу», а ручное исправление DSDT-прошивки под FreeBSD достойно упоминания в летописях, поэтому если есть возможность для подобных экспериментов — стоит ею воспользоваться.

Нормальный путь

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

В современных процессорах есть встроенная защита от перегрева, автоматически отключающая чип при превышении порогового значения, которое обычно находится в пределах 90-100 ℃. Именно его нужно указать в качестве переопределенного значения _CRT для того чтобы проблема исчезла.

Но для этого сначала необходимо включить опцию переопределения значений сенсоров:

sysctl hw.acpi.thermal.user_override=1

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

sysctl hw.acpi.thermal.tz0._CRT=100C

Чтобы настройка сохранилась при перезагрузке системы, добавляем эти параметры в /etc/sysctl.conf.

К сожалению значение hw.acpi.thermal.tz0._CRT будет сбрасываться при каждом пробуждении ноутбука, поэтому повторную установку значения необходимо добавить еще и в скрипт /etc/rc.resume.

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

Кровавый патчинг

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

К сожалению ныне она недоступна и отсутствует даже в кэше Гугла, поэтому придется ссылаться на менее подробную версию японского автора и восстанавливать по памяти.

Весь необходимый инструментарий в виде утилит acpidump и iasl поставляется в FreeBSD вместе с базовой системой, поэтому ничего дополнительно устанавливать не придется.

Собственно iasl это и есть компилятор/декомпилятор для прошивки DSDT от Intel, а acpidump нужен для получения прошивки из памяти запущенной системы.

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

acpidump -dt > acpi-orig.asl

Оригинал лучше всего сразу скопировать:

cp acpi-orig.asl acpi-patched.asl

Полученный довольно объемный файл acpi-orig.asl будет содержать вполне читаемый код, содержащий обработчики событий:

/*
  RSD PTR: OEM=HP, ACPI_Rev=2.0x (2)
  XSDT=0x000000007f7e57c8, length=36, cksum=192
*/

В том числе тут будет метод, отвечающий за получение значения критической температуры процессора:

..
Method (_CRT, 0, Serialized)  // _CRT: Critical Temperature
            {
                Return (C315 (0x04, 0x00))
            }
..            

Таких методов в ASL-файле несколько, по одному на каждую термальную зону (ThermalZone), нам нужна нулевая (TZ0).

Как видно из фрагмента выше, тут используется вызов некого метода C315 (оригинальное название не восстановилось при декомпиляции), результат работы которого отдается наружу.

Сам метод C315 выглядит следующим образом:

Method (C315, 2, Serialized)
        {
            Local0 = 0x01
            Local1 = Arg0
            Local3 = DerefOf (C311 [Arg1])
            If ((Local3 == 0xFFFFFFFD))
            {
                Local3 = 0x00
            }

            If ((Arg0 < Local3))
            {
                Local0 = 0x00
                Local1 = (Arg0 + 0x01)
            }

            Local2 = DerefOf (DerefOf (DerefOf (C304 [C316 (Arg1)]) [
                Local0]) [Local1])
            Return (Local2)
        }

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

Local2 = DerefOf (DerefOf (DerefOf (C304 [C316 (Arg1)]) [
                Local0]) [Local1])
            Return (Local2)

Вот так выглядит начало C304, описывающего структуру данных с предопределенными значениями:

 Scope (\_TZ)
    {
        Name (C304, Package (0x04)
        {
            Package (0x02)
            {
                Package (0x05)
                {
                    0x05AC, 
                    0x00, 
                    0x00, 
                    0x00, 
                    0x00
                }, 
..               

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

Что же касается самих значений, цитируя японского автора:

中身を眺めると、 ThermalZone (TZ0) の中が記事で参照しているところと同様のようです。単位は0.1Kのようなので、90℃=363.15K=3631.5*0.1K を返すようにしてみます。

Таким образом метод 315 возвращает значение критической температуры по Кельвину и поскольку заранее известно, что при 90-100 градусах сработает автоматика процессора, достаточно просто возвращать число 90, преобразовав его в Кельвины:

 Method (_CRT, 0, Serialized)  // _CRT: Critical Temperature
             {
-                Return (C315 (0x04, 0x00))
+                Return (3632)
             }

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

Ну конечно же нет.

Продолжение кровавого патчинга

Первая же попытка скомпилировать прошивку обратно:

iasl acpi-orig.asl

быстро познакомит с суровой реальностью:

Compilation failed. 5 Errors, 40 Warnings, 73 Remarks
No AML files were generated due to compiler error(s)

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

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

Рабочая область

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

acpi-patched.asl 612: OperationRegion (C066, SystemMemory, C02D (), 0x05DA)
Error 6080 - Called method returns no value ^  (\_SB.C02D)

Открываем метод C02D и видим, что по какой-то причине тут и правда нет возвращаемого значения:

Method (C02D, 0, NotSerialized)
        {
            Local0 = (C02A + 0x00014F14)
        }

Для исправления достаточно добавить очевидное Return (Local0) в конец метода.

acpi-patched.asl 834: 0x00000000,         // Length
Error 6043 - ^ Invalid combination of Length and Min/Max fixed flags

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

длина блока указана нулем (0x00000000), но при этом указаны параметры MinFixed и MaxFixed.

Для исправления необходимо заменить MinFixed и MaxFixed на MinNotFixed и MaxNotFixed.

acpi-patched.asl   5231: Device (C21B)
Error 6141 - Missing dependency ^  (Device object requires a _HID or _ADR)

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

При этом значение поля доступно чуть ниже в блоке Scope (\_SB.C002.C0DE):

Name (_ADR, 0x001F0001)

После этой правки компиляция пройдет успешно, несмотря на кучу предупреждений:

Compilation successful. 0 Errors, 39 Warnings, 73 Remarks, 2347 Optimizations, 12 Constants Folded

В каталоге появится файл acpi-patched.aml - та самая бинарная прошивка с байткодом, которую можно загрузить в устройство.

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

diff -ruN acpi-orig.asl acpi-patched.asl > acpi-patch.diff

Весь diff целиком:

--- acpi-orig.asl	2026-09-14 02:42:03.319944000 +0300
+++ acpi-patched.asl	2026-09-14 03:11:54.645339000 +0300
@@ -517,6 +517,7 @@
         Method (C02D, 0, NotSerialized)
         {
             Local0 = (C02A + 0x00014F14)
+            Return (Local0)
         }
 
         Method (C02E, 0, NotSerialized)
@@ -826,14 +827,14 @@
                     0x00000000,         // Translation Offset
                     0x00020000,         // Length
                     ,, , AddressRangeMemory, TypeStatic)
-                DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
+                DWordMemory (ResourceProducer, PosDecode, MinNotFixed, MaxNotFixed, Cacheable, ReadWrite,
                     0x00000000,         // Granularity
                     0x00000000,         // Range Minimum
                     0xFEDFFFFF,         // Range Maximum
                     0x00000000,         // Translation Offset
                     0x00000000,         // Length
                     ,, _Y02, AddressRangeMemory, TypeStatic)
-                DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
+                DWordMemory (ResourceProducer, PosDecode, MinNotFixed, MaxNotFixed, Cacheable, ReadWrite,
                     0x00000000,         // Granularity
                     0xFEE01000,         // Range Minimum
                     0xFFFFFFFF,         // Range Maximum
@@ -1256,6 +1257,7 @@
 
             Device (C0DE)
             {
+                Name (_ADR, 0x001F0001)  // _ADR: Address
                 OperationRegion (C0DF, PCI_Config, 0x40, 0x18)
                 Field (C0DF, AnyAcc, NoLock, Preserve)
                 {
@@ -5230,6 +5232,7 @@
 
                 Device (C21B)
                 {
+                    Name (_ADR, 0x00)  // _ADR: Address
                     Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
                     {
                         IRQNoFlags ()
@@ -12789,7 +12792,7 @@
 
             Method (_CRT, 0, Serialized)  // _CRT: Critical Temperature
             {
-                Return (C315 (0x04, 0x00))
+                Return (3632)
             }
 
             Method (_TMP, 0, Serialized)  // _TMP: Temperature
@@ -13803,7 +13806,6 @@
 
     Scope (\_SB.C002.C0DE)
     {
-        Name (_ADR, 0x001F0001)  // _ADR: Address
         Name (C34F, 0x01)
         Device (C345)
         {

Теперь переходим к проверке всей этой радости.

Установка и проверка

Тут все на удивление просто: копируем полученный файл с прошивкой в каталог /boot, затем включаем опцию загрузки кастомной DSDT-прошивки:

# Load fixed ACPI tables to remove erroneous trip temperature reports
acpi_dsdt_load="YES"
acpi_dsdt_name="/boot/acpi-patched.aml"

После перезагрузки системы будет загружена наша кастомная прошивка и проблема с забиванием буфера ядра повторяющимися сообщениями:

_CRT value is absurd, ignored (256.1C)

будет окончательно решена.

Также проверить работу патча также можно посмотрев отдаваемое значение поля _CRT:

 sysctl hw.acpi.thermal.tz0._CRT

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