1101936476 发表于 2018-3-10 17:55

[翻译]使用Clover“热修复”DSDT -Experimental Version

本帖最后由 1101936476 于 2018-3-10 20:04 编辑

原帖在 Using Clover to "hotpatch" ACPI https://www.tonymacx86.com/threads/guide-using-clover-to-hotpatch-acpi.200137/翻译过程中参考了http://www.tonymacx86.com/threads/guide-patching-laptop-dsdt-ssdts.152573/
说明:本人也看不太懂,而且还没有正式开始尝试给笔记本装macOS,我只是先把英文材料尝试翻译一遍,方便以后慢慢研究学习,如果能给各位提供便利那我深感荣幸,如果翻译不到位的地方,也麻烦各位不吝赐教。      Upadte in 18-03-10 20:04

上述教程使用了所谓”静态补丁”。为注入打好补丁的ACPI文件,我们需要提取原始ACPI、拆分、更改、重新编译然后替换/ACPI/patched中的文件,让Clover注入修改的ACPI文件而不是原生文件。而利用本文的技术,我们可以跳过提取、拆分、再编译,直接更改BIOS中提供的ACPI二进制文件。

在尝试hotpatch之前,你要对ACPI静态补丁有充分了解。此外,你还要了解ACPI规范、二进制补丁、 编程以及ACPI概念。别指望在此帖中别人能一步步手把手教你。


Clover机制

Clover提供了一些方法来完成ACPI hotpatch:

- config.plist/ACPI/DSDT/Fixes
- config.plist/ACPI/DSDT/Patches
- ability to inject additional SSDTs

DSDT/Fixes提供ACPI补丁的修复功能。每个"Fix"都能完成特定的补丁来代替典型的MaciASL的补丁和静态补丁。例如,"IRQ Fix"可以通过"FixHPET_0010" "FixIPIC_0040""FIX_RTC_20000"和"FIX_TMR_40000"来完成。再如,"Fix _WAK Arg0 v2"可以通过"FIX_WAK_200000"来完成。你可以阅读Clover wiki了解关于其他补丁的更多信息。通常,基本功能并不需要许多DSDT "Fixes"。DSDT"Fixes"可用于ACPI/DSDT/Patches或附加SSDTs难以或不能实现的补丁。

DSDT/Patches允许Clover进行二进制搜索和替换。Clover载入原生ACPI文件、通过二进制搜索/替换来应用ACPI/Patches中声明的补丁,然后注入修复好的ACPI。你需要对二进制AML格式有所了解,这在ACPI规范(http://www.acpi.info/spec.htm)中有完整说明。

ACPI命名空间在载入时通过合并DSDT和SSDTs完成。通过添加附加SSDTs到ACPI/patched,我们可以在ACPI中添加代码。因为许多OS X补丁都使用_DSM方法向ioreg添加属性,所以只添加一个包含_DSM方法的SSDT要比给(整个)原生ACPI文件打补丁更科学。举个你熟悉的栗子就是Pike用ssdtPRgen.sh生成的SSDT.aml。

某些情况下,要实现一个目标可以有很多方法。 比如,你可以用二进制补丁去禁用或重命名原生ACPI中的组件, 然后用其他SSDTs替换它。


重命名ACPI对象

因为OSX的运行依赖于Mac中独有的ACPI对象名称,所以常见的一些补丁就是来重命名原生ACPI中的对象的。例如,许多笔记本用GFX0命名Intel集成显卡(Intel HD图形卡)。在OS X中,只有将显卡命名为IGPU才能启用Intel显卡的电源管理。使用静态补丁时,我们使用"Rename IGPU to GFX0"进行重命名。这个补丁必须应用到每个引用到GFX0的DSDT和SSDTs。

而通过hotpatch,我们只要用ACPI/DSDT/Patches中一个简单的Clover补丁就能将GFX0重命名为IGPU。 这些补丁能应用到DSDT和所有原生SSDTs(DSDT/Patches不会应用于ACPI/patched中的SSDTs)。这个补丁形如:

Comment: Rename GFX0 to IGPU
Find: <4746 5830>
Replace: <4947 5055>

查找和替换的十六进制值分别是GFX0和IGPU的ASCII码。

注:
u430:~ RehabMan$ echo -n GFX0|xxd
0000000: 4746 5830                               GFX0
u430:~ RehabMan$ echo -n IGPU|xxd
0000000: 4947 5055                               IGPU
许多常见的重命名大部分在Clover/hoptpatch项目的config.plist中:

https://github.com/RehabMan/OS-X-Clover-Laptop-Config/tree/master/hotpatch

事实上,该项目中SSDTs的hotpatch也依赖于重命名对象的实现。

常见的重命名:
GFX0 -> IGPU
SAT0 -> SATA
EHC1 -> EH01
EHC2 -> EH02
XHCI -> XHC
HECI -> IMEI
MEI -> IMEI
LPC -> LPCB
HDAS -> HDEF
AZAL -> HDEF

注意:所有ACPI标识符都是4个字符。不足者补以下划线。因此,形如XHC在AML二进制中表示为XHC_,EC表示为EC__,EC0表示为EC_,MEI表示为MEI_等等。


方法的删除

使用Clover二进制补丁很难删除ACPI对象(方法methods、命名names和设备devices等)。通常,我们必须添加_DSM方法来注入用来描述各种硬件的属性。但添加新的_DSM方法可能会与也许在原生ACPI文件中原有的_DSM方法冲突。若用静态补丁,则需使用"Remove _DSMmethods"。

既然删除方法很困难,而我们又不希望添加的_DSM和已有_DSM冲突,那么就要将已有_DSM重命名才能解决这个问题。

因此,我们再使用一次重命名补丁:

Comment: Rename _DSM to XDSM
Find: <5f44534d>
Replace: <5844534d>

有时你可以通过重命名一个对象来禁用它,以防它造成其他问题。例如,Intel DH67GD DSDT定义了一个APSS对象。如果这个对象保留在DSDT里面,将会干扰电源管理。我使用了APSS->APXX。因为AppleIntelCPUPowerManagement寻找APSS,而重命名为APXX就不会导致问题。


重定向和替换

在某些情况下,我们想替换代码改变ACPI的行为,为此,我们可以重命名某个对象,并在DSDT提供另一种实现方式。

一种常见的修复方式是通过改写DSDT和SSDTs的ACPI代码进行欺骗,使其表现得如同某一版本Windows的ACPI代码。 在静态补丁中我们会使用"OS Check Fix(Windows 8)"。应用时,它将代码:
If (_OSI("Windows 2012"))
改为:
If (LOr(_OSI("Darwin"),_OSI("Windows 2012"))
因为在OS X中_OSI的实现仅响应"Darwin",改变代码后这个特定的_OSI检查也同样会接受"Darwin"。

而hotpatch则采用了相反的方法。我们不是更改使用_OSI的代码,而是修改代码调用一个不同的方法来对Windows ACPI中的_OSI的实现进行模拟。

这种操作依赖于两种技术——一是使_OSI调用改为XOSI调用的补丁,二是使XOSI的实现模拟Windows的在某一确定版本中的功能。.

首先,修改代码使_OSI调用改为XOSI调用。

Comment: Change _OSI to XOSI
Find: <5f4f 5349>
Replace: <584f 5349>

查找和替换的十六进制值分别是_OSI和XOSI的ASCII码。

现在,前述代码由Clover修改后则变为:
If (XOSI("Windows 2012"))
这样,我们就需要一个可以实现XOSI的SSDT。你可以在repo源中(SSDT-XOSI.dsl)找到这样的实现。

要注意:若无实现XOSI方法的SSDT,(更改后的) XOSI的调用将导致ACPI终止(ACPI终止会导致ACPI方法的执行立即结束并产生错误)。所以别在没有XOSI方法时使用_OSI->XOSI。


重命名和替换

第二种方式,重命名和替换"Rename and Replace"类似于重定向和替换"Redirect and Replace"。在这种情况下, 我们不修改所有调用,而是更改方法的定义使其使用与原先不同的参数从而保持原有方法名。这允许作为调用目标的方法可以被替换。

例如,一个很常见的USB设备错误是“及时唤醒”("instant wake"),在唤醒之后USB将会被禁用。大多数笔记本的BIOS并不提供这个选项,所以控制这个功能的_PRW方法就须修复。

比如,原生_SB.PCI0.EHC1._PRW方法为:         
Method (_PRW, 0, NotSerialized) // _PRW: Power Resources for Wake
            {
                Return (GPRW (0x6D,0x03))
            }
为了给EHCI#1上的USB设备打补丁使其不致“及时唤醒”,须将代码修改为:
            
Method (_PRW, 0, NotSerialized) // _PRW: Power Resources for Wake
            {
                Return (GPRW (0x6D, 0))
            }
通常,其他几个调用GPRW的位置都须修改(请留意并非所有ACPI都使用GPRW)。然而我们可以不修改上述所有位置的调用,而是修改GPRW方法的定义:

原代码:
Method (GPRW, 2, NotSerialized)
      {
         ...
      }
如果改为:
Method (XPRW, 2, NotSerialized)
      {
         ...
      }
因为不希望修改所有位置的调用,所以这个补丁必须只在自己的方法中生效而不影响其他位置调用。根据ACPI规范,方法定义以字节码14开始,后附方法大小method size、方法名称method name、参数/标志argument count/flags。你可以在iasl中使用"-l"来生成ACPI文件的混合列表。例如:Lenovo u430 GPRW混合列表如下:
    4323:          Method (GPRW, 2,NotSerialized)

00003F95:14 45 08 47 50 52 57 02   ".E.GPRW."

    4324:          {
    4325:            Store(Arg0, Index (PRWP, Zero))

00003F9D:70 68 ..................    "ph"
00003F9F:88 50 52 57 50 00 00 ...    ".PRWP.."
我们可以利用报头字节method headerbytes完成查找替换:
Find: <14 45 08 47 50 52 57 02>
Replace: <14 45 08 58 50 52 57 02>

但若不同版本的BIOS和机器之间,方法极其相似而略有不同时如何处理?此时,因方法长度改变,就须修改14之后的字节。

我认为方法主体的开头不太可能比方法的总长度差异更大,因此可以在查找替换时从方法体中选择一些额外字节添加到查找文本:

Find: <47 50 52 57 02 70 68>
Replace: <58 50 52 57 02 70 68>

要使用的后续字节的数量取决于你需要用查找替换来修改的方法定义的数量。你可以在Hex Fiend等十六进制编辑器中查看原生AML二进制代码进行验证。

注意:尽管你可以仅搜索方法名+参数/标志,但仍有可能在其他你不希望修改的地方找到相同结果。而u430并不存在这种情况,所以我能只查找替换方法名+标志。

Find: <47505257 02>
Replace: <58505257 02>
对于ProBook的UPRW,查找时须加入方法体中后续字节:

Find: <55505257 0a7012>
Replace: <58505257 0a7012>
现在任何调用GPRW(或ProBook中的UPRW)的代码都会因为名称不匹配而无法调用XPRW。而XPRW 现在则不可用。这意味着GPRW就能修改成我们要完成的目标:
Method(GPRW, 2)
{
    If (0x6d == Arg0) { Return(Package() { 0x6d, 0, }) }
    External(\XPRW, MethodObj)
    Return(XPRW(Arg0, Arg1))
}
在这里解释一下代码:对于任何调用第一个参数设置为0x6d的GPRW(GPE我们要尝试禁止唤醒),我们将返回一个含有0x6d和0(用于禁止唤醒)的集合,而非原本的GPRW。对于其他GPE值,代码仅调用更名为XPRW的GPRW方法。

再举一个简单例子,修改EC查询方法可以修复亮度键。将_Qxx简单地重命名为XQxx,再将原命名重新定义即可。

例如HP Envy Haswell:
      
      // _Q13 called on brightness/mirror display key
      Method (_Q13, 0, Serialized)// _Qxx: ECQuery
      {
            External(\HKNO, FieldUnitObj)
            Store(HKNO, Local0)
            If (LEqual(Local0,7))
            {
                // Brightness Down
               Notify(\_SB.PCI0.LPCB.PS2K, 0x0405)
            }
            If (LEqual(Local0,8))
            {
                // Brightness Up
               Notify(\_SB.PCI0.LPCB.PS2K, 0x0406)
            }
            If (LEqual(Local0,4))
            {
                // Mirror toggle
               Notify(\_SB.PCI0.LPCB.PS2K, 0x046e)
            }
      }
相关补丁:
Comment: change Method(_Q13,0,S) to XQ13
Find: <5f513133 08>
Replace: <58513133 08>
这种重命名和替换还可在更多更复杂的情况下使用。一个典型的例子就是修复电池方法时,使用这样的方式可以避免涉及到多字节EC字段。


对于复杂重命名和替换的建议

如你已知,修复电池状态(多字节EC字段)可能很复杂且涉及到许多方法的修改。

本节将详述一些修复电池的技术和程序。

在此不建议先用hotpatch直接打补丁。你应该在它(静态补丁)生效之后再试hotpatch。同样的,对比电池代码修复前后的差别也非常有意义。你可以使用诸如'diffmerge'这样的工具来进行比对。如果我的笔记本代码库中已经有了针对你的笔记本的静态电池补丁,那么这样做就更加万无一失。

一般程序:
- 取得原生ACPI
- 给电池状态使用静态补丁修复(并且保证它起效)
- 使用diffmerge比较修复前后的代码
- 对于每个不同的方法,尝试实现重命名和替换
- 对于EC字段,创建另一个EC操作区OperationRegion(使用和原来不同的命名)并将字段定义为仅包含需要修改的EC字段的“覆盖”"overlay"
- 要创造EC覆盖,你可以使用已修复的DSDT里边的修复好的字段/操作区Field/OperationRegion,然后删除未修复的字段。
- 使用External来保证SSDT中的替代方法可以访问ACPI中其他位置(通常是DSDT)已定义的字段
- 让编译器指出需要用到External的地方
- 注意在不用域scope中拥有相同命名的标识符symbols(译者注:虽然原文不是identifier,但私以为标识符更合适)

本帖2楼有示例。


代码值修复Code valuepatching

鉴于OSX不能正确地支持Mutex调试并且含有任何非零SyncLevel的Mutex都会导致系统终止,我们要用"Fix Mutex withnon-zero SyncLevel"这个补丁,此补丁查找所有Mutex对象并将SyncLevel替换为0。

例如,u430声明了如下Mutex:      
Mutex (MSMI, 0x07)
要使其兼容OS X,则须改为:
Mutex (MSMI, 0)
ACPI规范中定义了Mutex在AML中如何编码,但这有利于查看混合拆分的小ACPI文件:
DefinitionBlock ("", "DSDT", 2, "test","test", 0)
{
    Mutex(ABCD, 7)
}
Iasl编译器可以使用"-l"选项来创建一个混合列表文件。

如果我们用iasl -l test.dsl命令来编译上述文件,则test.lst将包含:

       1:DefinitionBlock ("","DSDT", 2, "test", "test", 0)
00000000:44 53 44 54 2B 00 00 00   "DSDT+..."
00000008:02 36 74 65 73 74 00 00   ".6test.."
00000010:74 65 73 74 00 00 00 00   "test...."
00000018:00 00 00 00 49 4E 54 4C   "....INTL"
00000020:10 04 16 20 ............    "... "

       2:{
      3:      Mutex(ABCD, 7)

00000024:5B 01 41 42 43 44 07 ...    "[.ABCD."
       4:}
如你所见,Mutex(ABCD, 7)的编码是<5B 01 41 42 43 44 07>

这样就可以轻而易举地修复:

Comment: Change Mutex(ABCD,7) to Mutex(ABCD,0)
Find: <5B 01 41 42 43 44 07>
Replace: <5B 01 41 42 43 44 00>

Clover ACPI配置

若用静态补丁,需使用DropOem=true并且修复的DSDT和SSDTs要添加到ACPI/patched。而用hotpatch时,则使用DropOem=false并且只要将附加的SSDTs放置在ACPI/patched。

要注意的是config.plist/ACPI/patches仅应用到原生SSDTs而非ACPI/patched的SSDTs。这意味着若你在config.plist中重命名对象,则附加SSDTs必须引用新命名而非旧命名。不同于ACPI/patched的SSDTs,ACPI/Patches中的补丁将会应用到可能放置在ACPI/patched的DSDT.aml。若你联合使用静态补丁和hotpatch请留意。

其次,使用静态补丁时,SortedOrder用于指定ACPI/patched下SSDTs的顺序。而hotpatch并不对此严格要求,因为它可以在每个SSDT都完成代码的修复而且这些代码都不依赖于(SSDT的)顺序——尤其是你所有补丁代码(比如我的笔记本repo许多范例代码)添加到单个SSDT时。除非你添加的SSDTs是顺序依赖的,否则你没有必要在SortedOrder中命名每一个SSDT。

同时,你也没有必要为每个SSDT选择一个序号命名。因此你可以给SSDTs有意义的命名,如SSDT-USB.aml、SSDT-XOSI.aml。使用序号命名只会将你自己搞懵逼,所以别自找麻烦。


问题排除

你可以使用patchmatic查看全部的修复后由Clover注入ACPI。通过运行patchmatic -extract,patchmatic会写入所有注入的DSDT.aml和由Clover按顺序注入的SSDT*.aml。你可以通过iasl -da -dl *.aml'拆分它们。如果iasl在拆分时报错(比如说存在重复标识符symbols),那么OS X也同样可能拒绝这些冲突的SSDTs。

如果你对hotpatch了解尚浅,那你最好一次性只实现一个补丁,然后一步步完成对全部补丁和SSDTs的修改。追求一步到位会让你难以定位你的错误。

1101936476 发表于 2018-3-10 18:50

本帖最后由 1101936476 于 2018-3-10 22:44 编辑

电池状态Hotpatch

2楼将描述电池状态hotpatch。为了演示过程,我会使用一个样例DSDT。此样例文件来自:

https://www.tonymacx86.com/threads/guide-disabling-discrete-graphics-in-dual-gpu-laptops.163772/

你需要下载附件中ACPI/origin的文件才能跟上节奏。

如1楼提及,一般步骤是:
-取得原生ACPI
- 给电池状态使用静态补丁修复(并且保证它起效)
- 使用diffmerge比较修复前后的代码
- 对于每个不同的方法,尝试实现重命名和替换模式
- 对于EC字段,创建另一个EC操作区OperationRegion(使用和原来不同的命名)并将字段定义为仅包含需要修改的EC字段的“覆盖”"overlay"
- 要创造EC覆盖,你可以使用已修复的DSDT里边的修复好的字段/操作区Field/OperationRegion,然后删除未修复的字段。
- 使用External来保证SSDT中的替代方法可以访问ACPI中其他位置(通常是DSDT)已定义的字段
- 让编译器指出需要用到External的地方
- 注意在不用域scope中拥有相同命名的标识符


使用diffmerge对比修复前后的差异

拆分源文件:iasl -da -dl *.aml。
(你需要熟悉这些,因为这是正常ACPI修复的一部分)

然后仅使用MaciASL将电池补丁应用到DSDT.dsl。此处,我们应用ASUS N55SL/VivoBook。这里没有任何错误需要修复,所以我们只需关注补丁做出的改动。将打补丁的文件保存为DSDT_patched.dsl。

现在你可以运行diffmerge进行比对。我一般在终端中进行:
diffmerge DSDT.dsl DSDT_patched.dsl

在此处,我们能通过点击左栏标记来检查修改的部分。

在案例中,可以找到如下几组变更:
- 组1:即EC字段的修改(多字节至单字节)
- 组2:添加了RDBA、WRBA、RDBB、WRBB方法
- 组3:修复了FBST、_BIX、B1FA方法
- 组4:修复了SMBR、 SMBW、ECSB方法
- 组5:修复了TACH方法
- 组6:添加了B1B2方法


创建初始SSDT

在MaciASL中建立空白SSDT:
DefinitionBlock("", "SSDT", 2, "hack",
"batt", 0)

{

}
然后添加所有补丁添加的方法。本例即RDBA、WRBA、RDBB、WRBB、B1B2。可直接从DSDT_patched.dsl中复制。

你想要确定每个方法都放置在同一个域。例如,以下是组2添加的方法:

见附件 - 组2
以下是B1B2:
见附件 - B1B2 别担心现在还不能编译这些代码——因为EC字段(以及其他标识符)在文件中尚未定义。它们需要(最终)由External定义或引用。

现在来添加修复的方法。操作同上,从DSDT_patched.dsl复制即可:
见附件 - 修复方法

注意FBST和_BIX为何添加到域_SB.PCI0.BAT0而BIFA添加到_SB.PCI0.LPCB.EC0。将所有的方法注入到原本的域至关重要。

现在添加SMBR、SMBW、ECSB和TACH:
见附件 - SMBR、SMBW、ECSB、TACH

青藤舜 发表于 2018-3-10 18:55

感谢楼主分享,先占楼,改天满满研究,&#128513;

dadong2005 发表于 2018-3-10 19:46

谢谢楼主分享,虽然看不太明白

p3181625 发表于 2018-3-13 17:46

是个好帖子啊

sleepnet 发表于 2018-3-13 18:58

翻译的很好,加油。

mrmg 发表于 2018-3-13 19:50

期待此类技术贴越来越多。

xuezou 发表于 2018-4-13 19:53

跟踪查看

imeeior 发表于 2018-4-13 20:08

压根看不懂啊

wdtx 发表于 2018-4-13 20:43

技术贴,可惜一时半会学不会。。。

dlhonghan 发表于 2018-6-11 12:52

来学习了   

小煦 发表于 2018-6-11 20:33

技术指导要支持

abcong 发表于 2018-7-13 22:26

顶,技术贴,可惜理解能力有限好多看不懂。

kidddjh 发表于 2018-7-14 00:28

让更多人看到

halleyjb 发表于 2019-3-30 08:48

技术贴,可惜一时半会学不会。。。

lingxun 发表于 2019-8-20 01:25

感谢分享,楼主辛苦了。

key_shadow 发表于 2019-12-3 20:34

这么好的帖子没人支持一下

zhanglei138099 发表于 2020-2-9 09:59

谢谢分享   
页: [1]
查看完整版本: [翻译]使用Clover“热修复”DSDT -Experimental Version