通过打DSDT补丁让黑苹果显示电池状态信息

翻译自:https://www.tonymacx86.com/threads/guide-how-to-patch-dsdt-for-working-battery-status.116102/

转载请注明出处

背景

因为电脑中的电池硬件与苹果的 SMbus 硬件不兼容,所以在笔记本电脑上运行 OS X 时,我们使用 ACPI 来访问电池状态。一般来说,我建议你使用 ACPIBatteryManager.kext,可在这里找到: https://github.com/RehabMan/OS-X-ACPI-Battery-Driver

AppleACPIPlatform 的更高版本无法正确访问 EC(嵌入式控制器)中的字段。由于各种 ACPI 方法(_BIF,_STA,_BST等)失效,这会导致 ACPIBatteryManager 获取电池数据出现问题。尽管可以使用较旧版本的 AppleACPIPlatform(来自 Snow Leopard),但还是希望使用最新版本的AppleACPIPlatform,因为对于具有 Ivy Bridge CPU 的计算机,它可以为这些计算机启用本机电源管理。要使用最新版本,必须更改 DSDT 以符合 Apple 的 AppleACPIPlatform 的限制。

特别是,EC 中大于 8 位的任何字段都必须更改为 8 位。 这包括 16、32、64 甚至更大的字段。

你应该先熟悉 DSDT/SSDT 打补丁的基本知识: http://www.tonymacx86.com/yosemite-laptop-support/152573-guide-patching-laptop-dsdt-ssdts.html

已存在的补丁

首先,你的笔记本电脑可能已经有补丁了。请看我的补丁仓库: https://github.com/RehabMan/Laptop-DSDT-Patch

为了使 DSDT 与补丁程序匹配,通常首先需要了解补丁程序的制作方式,以便你知道在 DSDT 中要查找的内容,并且可以将所看到的内容与现有补丁程序进行匹配。补丁集的更改比率很高,不会产生错误,并且似乎在补丁所有需要补丁的字段,这很可能是匹配的(本句原文:A patch set that has a high ratio of changes to patches, creates no errors, and appears to patch all fields that need to be patched is likely a match)。

更多信息: https://www.tonymacx86.com/threads/guide-how-to-patch-dsdt-for-working-battery-status.116102/page-333#post-1360697

注意不要使用除 MaciASL 以外的任何其他程序,包括 DSDT Editor,我只在 MaciASL 中测试了我的补丁

其他相关 DSDT 补丁

除了多字节 EC 字段外,还有一些其他 DSDT 问题可能会影响电池状态。这些特定问题并非特定于电池状态,但通常在尝试实现电池状态时首次注意到。

电池代码可能取决于将 Windows 的公认版本用作主机 OS。 要解决此问题,请从 laptop DSDT patch repository 中应用 “OS Check Fix” 补丁。 这将导致 DSDT 采取与运行 “Windows 2006” 时相同的操作。你可以更改补丁以实现不同的选择(例如”Windows 2012”)。

另一个常见的问题是,OS X 的 ACPI 实现很难处理使用非零 SyncLevel 声明的 Mutex 对象(有关更多信息,请阅读ACPI规范)。要解决此问题,请从 laptop DSDT patch repository 中应用 “Fix Mutex with non-zero SyncLevel” 补丁。

技能要求

DSDT 是一个“程序”。 因此,在进行修改时具有一些编程/计算机技能会很有帮助。同样,DSDT 补丁本身也具有自己的语言(在 MaciASL Wiki 中进行了简要说明,可在此处找到:http://sourceforge.net/projects/maciasl/ )。最后,补丁本身基本上是正则表达式的搜索/替换,因此理解正则表达式(regex)会有帮助。熟悉编译器、编译器错误、有能力确定编译器报告的有关代码错误也很有用。

另外,熟悉 ACPI 也是一个好注意。可以在此处下载规范:https://www.acpica.org/

本指南的目的不是教你基本的编程技巧,正则表达式或 ACPI 语言。

打补丁的步骤

我使用了一种相当“机械”的过程来打 DSDT 补丁。我只是寻找那些 OS X 无法处理的部分并机械地转换它。我不会太努力地确定代码的哪些部分实际上将要执行,我只是转换了所有看到的内容。

要继续学习,请从本文中下载示例 DSDT。此特定的 DSDT 示例适用于 HP Envy14。最终的完整补丁可从我的补丁库中以获取 “HP Envy 14”。

首先确定 DSDT 中看起来需要修改的结构。加载 DSDT 文件到 MaciASL 并搜索 EmbeddedControl。单个 DSDT 中可以有多个 EmbeddedControl 部分,每个部分都附加了字段声明。

因此,我总是从寻找 embeddedcontrol 开始以找到这种声明。

在示例 DSDT 中,您将找到以下单个 EC 域:

1
OperationRegion (ECF2, EmbeddedControl, Zero, 0xFF)

上面的代码声明了一个 255 字节的 EC 域。

这个域被称为 ECF2,所以现在我们要搜索 'Field (ECF2'。正如在示例 DSDT 中可以看到的,只有一个结构定义(译者注:下面的代码块)引用了这个域,在其他的 DSDT 中可能有很多。

结构的定义描述了这个 255 字节 EC 域。它们是有关联的,因为这个结构体定义引用了 ECF2 这个名字(本句原文:You can tell it is related because the name ECF2 is referred to by the Field.)。可以将其视为 EC 中的一种结构(C程序员的结构)(译者注:可以将上面的代码块看作是声明,下面的代码块看作是定义,这样可能更容易理解一些)。

下一步是检查结构定义中的字段,找到大于 8 位的字段。例如,声明的第一个字段是 BDN0,大小为 56 位:

1
2
3
4
Field (ECF2, ByteAcc, Lock, Preserve)
{
Offset (0x10),
BDN0, 56,

这是一个大于 8 位的(56 位)字段, 如果这个字段在 DSDT 中的其他位置被访问,则所有出现这个字段的代码都要修改,如果继续搜索 "BDN0" 会找到:

1
Store (BDN0, BDN)

这是在(从结构 ECF2 中)将 BDN0 中的值存储到 BDN 中。当访问大于 32 位的字段时,它们以 Buffer 类型访问。32 位或以下的字段作为整数访问。在更改代码时,这一点很重要。Buffer 还有一些其他工作要做。同样,需要注意到此代码是从 ECF2 中读取的。从 ECF2 中读取和写入这两种操作必须以不同的方式处理。

因此,针对这行代码,我们的目的是一次读 8 位读 7 次将这个 56 位的元素读取到缓冲区,以便将所得缓冲区存储到 BDN 中。我们一会儿再回来看如何修改,现在让我们探索 ECF2 结构定义中的其他字段。

回到 ECF2 的定义的位置,查看 ECF2 定义的其余部分,查找所有大于 8 位的字段,然后针对每个字段搜索 DSDT 的其余部分,以查看它们是否在其他地方被访问。通常对于那些没有被其他地方访问的字段,我们无需执行任何操作。 因此,我们看到的下一个字段是BMN0:

1
BMN0,   32,

如果我们在 DSDT 中搜索 BMN0,则只会找到此声明,因此它没有在其他地方被访问,我们可以忽略它。BMN4 也可以被忽略。而 BCT0 是128位并且在其他地方被访问,就像最初的 BDN0 一样:

1
Store (BCT0, CTN)

进一步的搜索将得到以下列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
                        BDN0,   56,
BCT0, 128,
BDN1, 56,
BCT1, 128,
...
BDC0, 16,
BDC1, 16,
BFC0, 16,
BFC1, 16,
...
BDV0, 16,
BDV1, 16,
...
BPR0, 16,
BPR1, 16,

BRC0, 16,
BRC1, 16,
BCC0, 16,
BCC1, 16,
CV01, 16,
CV02, 16,
CV03, 16,
CV04, 16,
CV11, 16,
CV12, 16,
CV13, 16,
CV14, 16,
...
BMD0, 16,
BMD1, 16,
BPV0, 16,
BPV1, 16,
BSN0, 16,
BSN1, 16,
BCV0, 16,
BCV1, 16,
CRG0, 16,
CRG1, 16,
BTY0, 32,
BTY1, 32,
...
CBT0, 16,
CBT1, 16,

正如你所看到的,此 DSDT 中有很多字段需要处理,并且大小各异,16位,32位,56位和128位。

大小为16位和32位的字段

16位和32位的字段最容易处理,所以让我们从这里开始。让我们以上面列表中的第一个16位字段 BDC0 为例。我们要做的是更改此字段,以便将其分为两个部分(低字节,高字节)。为此,我们需要提供一个 4 个字符的名称,该名称不得与 DSDT 中的任何其他名称冲突,这通常很容易只需删除第一个字母并使用后三个字母。(译者注:把一个大小为 16 的变量,拆分两个大小为 8 的变量,并使用不同的变量名)

1
2
3
// 之前是: BDC0, 16
DC00, 8,
DC01, 8,

针对它的补丁可以这样写:

1
into device label H_EC code_regex BDC0,\s+16, replace_matched begin DC00,8,DC01,8, end;

这个补丁的意思是:在名为 H_ECdevice 段中,查找 BDC0,\s+16(其中 \s+ 表示一个或多个空白符号),如果找到了则将其替换为 DC00,8,DC01,8,,这有效的将一个字段分成了两个。如果你应用这个补丁,并尝试编译修改后的 DSDT 文件,会发现一些错误,因为依然有代码在引用/访问 BDC0(译者注:BDC0 已经不存在了,因为被拆成了 DC00DC01),这些错误实际上在帮助我们找到有哪地方需要修改:

1
2
Store (BDC0, Index (DerefOf (Index (Local0, 0x02)), Zero))
Store (ShiftRight (BDC0, 0x08), Index (DerefOf (Index (Local0, 0x02)), One))

正如你所看到的,这段代码依然在读取已经不存在的、被拆成两部分的 BDC0。为了使补丁更容易编写,我们使用了一个名叫 B1B2 的工具函数,使用如下补丁来定义这个函数:

1
2
3
4
5
into method label B1B2 remove_entry;
into definitionblock code_regex . insert
begin
Method (B1B2, 2, NotSerialized) { Return(Or(Arg0, ShiftLeft(Arg1, 8))) }\n
end;

这个函数接收两个参数:低字节和高字节,并返回一个包含这两个参数的 16 位的值。(译者注:这个函数的作用是我们可以用这个函数来替换代码中所有对 BDC0 的引用)

针对上面的代码(译者注:注意是代码,不是补丁),我们想要将它转换成这样:

1
2
Store (B1B2(DC00,DC01), Index (DerefOf (Index (Local0, 0x02)), Zero))
Store (ShiftRight (B1B2(DC00,DC01), 0x08), Index (DerefOf (Index (Local0, 0x02)), One))

构建一个补丁来自动执行这个转换,并且其他 16 位字段的补丁也将被应用相同的模式:
(译者注:注意这里使用的是 replaceall_matched,而上面使用的是 replace_matched,它们的区别从字面及原作者的语义来看前者将对 DSDT 文件中的所有匹配做替换操作,后者只替换一个)

1
into method label GBTI code_regex \(BDC0, replaceall_matched begin (B1B2(DC00,DC01), end;

敏锐的读者会注意到,可以对以下代码进行优化(译者注:这里没看懂,估计要结合示例 DSDT 文件内容来看才能理解):

1
2
Store (DC00, Index (DerefOf (Index (Local0, 0x02)), Zero))
Store (DC01, Index (DerefOf (Index (Local0, 0x02)), One))

这种优化只能通过手动进行,通常这是不值得的。这里的目标是提出一种修复此代码的自动化方法,而不是试图过多地手动修改,因为如果我们进行过多的手动更改,我们可能会在代码中引入错误。另外,这种代码很少见(在我修改过的 20 多个 DSDT 中仅在两个 DSDT 中看到了它)。

既然你了解了如何处理 16 位的字段,那么将它们全部转换可能是最简单的。这是针对 16 位 EC 字段的综合补丁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 16-bit registers
into device label H_EC code_regex BDC0,\s+16 replace_matched begin DC00,8,DC01,8 end;
into device label H_EC code_regex BDC1,\s+16 replace_matched begin DC10,8,DC11,8 end;
into device label H_EC code_regex BFC0,\s+16 replace_matched begin FC00,8,FC01,8 end;
into device label H_EC code_regex BFC1,\s+16 replace_matched begin FC10,8,FC11,8 end;
into device label H_EC code_regex BDV0,\s+16 replace_matched begin DV00,8,DV01,8 end;
into device label H_EC code_regex BDV1,\s+16 replace_matched begin DV10,8,DV11,8 end;
into device label H_EC code_regex BPR0,\s+16 replace_matched begin PR00,8,PR01,8 end;
into device label H_EC code_regex BPR1,\s+16 replace_matched begin PR10,8,PR11,8 end;
into device label H_EC code_regex BRC0,\s+16 replace_matched begin RC00,8,RC01,8 end;
into device label H_EC code_regex BRC1,\s+16 replace_matched begin RC10,8,RC11,8 end;
into device label H_EC code_regex BCC0,\s+16 replace_matched begin CC00,8,CC01,8 end;
into device label H_EC code_regex BCC1,\s+16 replace_matched begin CC10,8,CC11,8 end;
into device label H_EC code_regex CV01,\s+16 replace_matched begin CV10,8,CV11,8 end;
into device label H_EC code_regex CV02,\s+16 replace_matched begin CV20,8,CV21,8 end;
into device label H_EC code_regex CV03,\s+16 replace_matched begin CV30,8,CV31,8 end;
into device label H_EC code_regex CV04,\s+16 replace_matched begin CV40,8,CV41,8 end;
into device label H_EC code_regex CV11,\s+16 replace_matched begin CV50,8,CV51,8 end;
into device label H_EC code_regex CV12,\s+16 replace_matched begin CV60,8,CV61,8 end;
into device label H_EC code_regex CV13,\s+16 replace_matched begin CV70,8,CV71,8 end;
into device label H_EC code_regex CV14,\s+16 replace_matched begin CV80,8,CV81,8 end;
into device label H_EC code_regex HPBA,\s+16 replace_matched begin PBA0,8,PBA1,8 end;
into device label H_EC code_regex HPBB,\s+16 replace_matched begin PBB0,8,PBB1,8 end;
into device label H_EC code_regex BMD0,\s+16 replace_matched begin MD00,8,MD01,8 end;
into device label H_EC code_regex BMD1,\s+16 replace_matched begin MD10,8,MD11,8 end;
into device label H_EC code_regex BPV0,\s+16 replace_matched begin PV00,8,PV01,8 end;
into device label H_EC code_regex BPV1,\s+16 replace_matched begin PV10,8,PV11,8 end;
into device label H_EC code_regex BSN0,\s+16 replace_matched begin SN00,8,SN01,8 end;
into device label H_EC code_regex BSN1,\s+16 replace_matched begin SN10,8,SN11,8 end;
into device label H_EC code_regex BCV0,\s+16 replace_matched begin BV00,8,BV01,8 end;
into device label H_EC code_regex BCV1,\s+16 replace_matched begin BV10,8,BV11,8 end;
into device label H_EC code_regex CRG0,\s+16 replace_matched begin RG00,8,RG01,8 end;
into device label H_EC code_regex CRG1,\s+16 replace_matched begin RG10,8,RG11,8 end;
into device label H_EC code_regex CBT0,\s+16 replace_matched begin BT00,8,BT01,8 end;
into device label H_EC code_regex CBT1,\s+16 replace_matched begin BT10,8,BT11,8 end;

并且访问这些字段的所有代码都必须修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# fix 16-bit methods
into method label GBTI code_regex \(BDC0, replaceall_matched begin (B1B2(DC00,DC01), end;
into method label GBTI code_regex \(BDC1, replaceall_matched begin (B1B2(DC10,DC11), end;
into method label GBTI code_regex \(BFC0, replaceall_matched begin (B1B2(FC00,FC01), end;
into method label GBTI code_regex \(BFC1, replaceall_matched begin (B1B2(FC10,FC11), end;
into method label BTIF code_regex \(BFC0, replaceall_matched begin (B1B2(FC00,FC01), end;
into method label BTIF code_regex \(BFC1, replaceall_matched begin (B1B2(FC10,FC11), end;
into method label ITLB code_regex \(BFC1, replaceall_matched begin (B1B2(FC10,FC11), end;
into method label ITLB code_regex \sBFC0, replaceall_matched begin B1B2(FC00,FC01), end;
into method label _Q09 code_regex \(BRC0, replaceall_matched begin (B1B2(RC00,RC01), end;
into method label _Q09 code_regex \sBFC0\) replaceall_matched begin B1B2(FC00,FC01)) end;
into method label GBTI code_regex \(BDV0, replaceall_matched begin (B1B2(DV00,DV01), end;
into method label GBTI code_regex \(BDV1, replaceall_matched begin (B1B2(DV10,DV11), end;
into method label BTIF code_regex \(BDV0, replaceall_matched begin (B1B2(DV00,DV01), end;
into method label BTIF code_regex \(BDV1, replaceall_matched begin (B1B2(DV10,DV11), end;
into method label GBTI code_regex \(BPR0, replaceall_matched begin (B1B2(PR00,PR01), end;
into method label GBTI code_regex \(BPR1, replaceall_matched begin (B1B2(PR10,PR11), end;
into method label BTST code_regex \sBPR0, replaceall_matched begin B1B2(PR00,PR01), end;
into method label BTST code_regex \sBPR1, replaceall_matched begin B1B2(PR10,PR11), end;
into method label BTST code_regex \(BPR0, replaceall_matched begin (B1B2(PR00,PR01), end;
into method label BTST code_regex \(BPR1, replaceall_matched begin (B1B2(PR10,PR11), end;
into method label BTST code_regex \(BRC0, replaceall_matched begin (B1B2(RC00,RC01), end;
into method label BTST code_regex \(BRC1, replaceall_matched begin (B1B2(RC10,RC11), end;
into method label GBTI code_regex \(BRC0, replaceall_matched begin (B1B2(RC00,RC01), end;
into method label GBTI code_regex \(BRC1, replaceall_matched begin (B1B2(RC10,RC11), end;
into method label _Q09 code_regex \(BRC0, replaceall_matched begin (B1B2(RC00,RC01), end;
into method label GBTI code_regex \(BCC0, replaceall_matched begin (B1B2(CC00,CC01), end;
into method label GBTI code_regex \(BCC1, replaceall_matched begin (B1B2(CC10,CC11), end;
into method label GBTI code_regex \(CV01, replaceall_matched begin (B1B2(CV10,CV11), end;
into method label GBTI code_regex \(CV02, replaceall_matched begin (B1B2(CV20,CV21), end;
into method label GBTI code_regex \(CV03, replaceall_matched begin (B1B2(CV30,CV31), end;
into method label GBTI code_regex \(CV04, replaceall_matched begin (B1B2(CV40,CV41), end;
into method label GBTI code_regex \(CV11, replaceall_matched begin (B1B2(CV50,CV51), end;
into method label GBTI code_regex \(CV12, replaceall_matched begin (B1B2(CV60,CV61), end;
into method label GBTI code_regex \(CV13, replaceall_matched begin (B1B2(CV70,CV71), end;
into method label GBTI code_regex \(CV14, replaceall_matched begin (B1B2(CV80,CV81), end;
into method label BTIF code_regex \(BMD0, replaceall_matched begin (B1B2(MD00,MD01), end;
into method label BTIF code_regex \(BMD1, replaceall_matched begin (B1B2(MD10,MD11), end;
into method label GBTI code_regex \sBMD0\) replaceall_matched begin B1B2(MD00,MD01)) end;
into method label GBTI code_regex \(BMD0, replaceall_matched begin (B1B2(MD00,MD01), end;
into method label GBTI code_regex \sBMD1\) replaceall_matched begin B1B2(MD10,MD11)) end;
into method label GBTI code_regex \(BMD1, replaceall_matched begin (B1B2(MD10,MD11), end;
into method label BTST code_regex \(BPV0, replaceall_matched begin (B1B2(PV00,PV01), end;
into method label BTST code_regex \(BPV1, replaceall_matched begin (B1B2(PV10,PV11), end;
into method label GBTI code_regex \(BPV0, replaceall_matched begin (B1B2(PV00,PV01), end;
into method label GBTI code_regex \(BPV1, replaceall_matched begin (B1B2(PV10,PV11), end;
into method label BTIF code_regex \(BSN0, replaceall_matched begin (B1B2(SN00,SN01), end;
into method label BTIF code_regex \(BSN1, replaceall_matched begin (B1B2(SN10,SN11), end;
into method label GBTI code_regex \(BSN0, replaceall_matched begin (B1B2(SN00,SN01), end;
into method label GBTI code_regex \(BSN1, replaceall_matched begin (B1B2(SN10,SN11), end;
into method label GBTI code_regex \(BCV0, replaceall_matched begin (B1B2(BV00,BV01), end;
into method label GBTI code_regex \(BCV1, replaceall_matched begin (B1B2(BV10,BV11), end;
into method label GBTI code_regex \(CRG0, replaceall_matched begin (B1B2(RG00,RG01), end;
into method label GBTI code_regex \(CRG1, replaceall_matched begin (B1B2(RG10,RG11), end;
into method label GBTI code_regex \(CBT0, replaceall_matched begin (B1B2(BT00,BT01), end;
into method label GBTI code_regex \(CBT1, replaceall_matched begin (B1B2(BT10,BT11), end;

现在那些 32 位的字段如 BTY0 和 BTY1 如何处理呢? 它们与 16 位的字段处理方式差不多,除了我们需要再声明一个名为 B1B4 的函数,它能从四个 8 位的参数中构造出一个 32 位的值:

1
2
3
4
5
6
7
8
9
10
11
12
into method label B1B4 remove_entry;
into definitionblock code_regex . insert
begin
Method (B1B4, 4, NotSerialized)\n
{\n
Store(Arg3, Local0)\n
Or(Arg2, ShiftLeft(Local0, 8), Local0)\n
Or(Arg1, ShiftLeft(Local0, 8), Local0)\n
Or(Arg0, ShiftLeft(Local0, 8), Local0)\n
Return(Local0)\n
}\n
end;

然后我们需要将 BTY0 和 BTY1 转换成 4 个 8 位的字段:

1
2
3
# 32-bit registers
into device label H_EC code_regex BTY0,\s+32 replace_matched begin TY00,8,TY01,8,TY02,8,TY03,8 end;
into device label H_EC code_regex BTY1,\s+32 replace_matched begin TY10,8,TY11,8,TY12,8,TY13,8 end;

下面这些在 GBTI 函数中的代码需要修改,因为它们引用了 BTY0 和 BYT1:

1
2
3
   Store (BTY0, BTY)
...
Store (BTY1, BTYB)

很像针对 16 位字段所做的补丁,但使用的是 B1B4 函数:

1
2
3
# fix 32-bit methods
into method label GBTI code_regex \(BTY0, replaceall_matched begin (B1B4(TY00,TY01,TY02,TY03), end;
into method label GBTI code_regex \(BTY1, replaceall_matched begin (B1B4(TY10,TY11,TY12,TY13), end;

这个补丁将会把上面的代码修改成下面这样:

1
2
3
   Store (B1B4(TY00,TY01,TY02,TY03), BTY)
...
Store (B1B4(TY10,TY11,TY12,TY13), BTYB)

缓冲字段(大于 32 位的字段)

回到我们最初对大于8位的字段的搜索结果,发现我们有这些大于32位的字段:

1
2
3
4
BDN0,   56,
BCT0, 128,
BDN1, 56,
BCT1, 128,

要一次以 8 位访问这些字段很繁琐,因此我喜欢通过 offset(偏移量) 来访问它们,并确保没有现有的代码直接访问它们,我们使用如下补丁将其重命名:

1
2
3
4
into device label H_EC code_regex (BDN0,)\s+(56) replace_matched begin BDNX,%2,//%1%2 end;
into device label H_EC code_regex (BDN1,)\s+(56) replace_matched begin BDNY,%2,//%1%2 end;
into device label H_EC code_regex (BCT0,)\s+(128) replace_matched begin BCTX,%2,//%1%2 end;
into device label H_EC code_regex (BCT1,)\s+(128) replace_matched begin BCTY,%2,//%1%2 end;

接下来,我们需要确定这些字段在结构体 ECF2 内的偏移量。请记住,大小以位为单位,但偏移量以字节为单位(译者注:这里需要记住)。我在下面的注释中使用的偏移量以十六进制表示。你可以看看是否能计算出相同的数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Field (ECF2, ByteAcc, Lock, Preserve)
{
Offset (0x10),
BDN0, 56, //!!0x10
Offset (0x18),
BME0, 8,
Offset (0x20),
BMN0, 32, //0x20
BMN2, 8, //0x24
BMN4, 88, //0x25
BCT0, 128, //!! 0x30
BDN1, 56, //!! 0x40
Offset (0x48),
BME1, 8,
Offset (0x50),
BMN1, 32, //0x50
BMN3, 8, //0x54
BMN5, 88, //0x55
BCT1, 128, //!!0x60

一旦你运行了上面的补丁并编译,编译器会告诉你哪些地方需要被注意(译者注:编译报错的地方即需要被修改的地方)。在这个例子中,我们会看到下面的错误:

1
2
3
4
5
6
7
8
9
...
Store (BCT0, CTN)
...
Store (BDN0, BDN)
...
Store (BCT1, CTNB)
...
Store (BDN1, BDNB)
...

出现这些错误是因为 BCT0, BDN0, BCT1, BDN1 这些字段被我们改了名字。

正如你所看到的,代码在从这些缓冲字段读取并将它们存储在其他位置。为了一次读取这些缓冲字段 8 位数据,我们需要定义其他函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# utility methods to read/write buffers from/to EC
into method label RE1B parent_label H_EC remove_entry;
into method label RECB parent_label H_EC remove_entry;
into device label H_EC insert
begin
Method (RE1B, 1, NotSerialized)\n
{\n
OperationRegion(ERAM, EmbeddedControl, Arg0, 1)\n
Field(ERAM, ByteAcc, NoLock, Preserve) { BYTE, 8 }\n
Return(BYTE)\n
}\n
Method (RECB, 2, Serialized)\n
// Arg0 - offset in bytes from zero-based EC\n
// Arg1 - size of buffer in bits\n
{\n
ShiftRight(Add(Arg1,7), 3, Arg1)\n
Name(TEMP, Buffer(Arg1) { })\n
Add(Arg0, Arg1, Arg1)\n
Store(0, Local0)\n
While (LLess(Arg0, Arg1))\n
{\n
Store(RE1B(Arg0), Index(TEMP, Local0))\n
Increment(Arg0)\n
Increment(Local0)\n
}\n
Return(TEMP)\n
}\n
end;

“RECB” 代表 “读 EC 缓冲区”。它接受两个参数,指示 EC 中的偏移量和希望读取的字段的位大小。以位为单位的大小必须是 8 的倍数。注意,函数中没有检查这个参数。

在此 DSDT 中,比如在名为 H_EC 的 EC 设备(译者注:代码段)中定义这些辅助方法:

1
2
3
Device (H_EC)
{
Name (_HID, EisaId ("PNP0C09"))

如果你的 EC 设备(译者注:代码段)名称不同(译者注:不叫 H_EC),则需要更改上面创建 RECB/RE1B 这两个函数的补丁。通常名称为 EC,EC0,在本例中为 H_EC。

为了处理 BCT0 的第一种情况,我们希望修改成这样:

1
Store(RECB(0x30,128), CTN)

0x30 是 BTC0 字段(现在称为 BCTX)的偏移量,而 128 是位数。

这些可以通过以下补丁来完成修改:

1
2
3
4
into method label GBTI code_regex \(BCT0, replaceall_matched begin (RECB(0x30,128), end;
into method label GBTI code_regex \(BCT1, replaceall_matched begin (RECB(0x60,128), end;
into method label GBTI code_regex \(BDN0, replaceall_matched begin (RECB(0x10,56), end;
into method label GBTI code_regex \(BDN1, replaceall_matched begin (RECB(0x40,56), end;

此 DSDT 没有对 EC 缓冲字段的任何写操作,但如果有,则下面的函数非常有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
into method label WE1B parent_label H_EC remove_entry;
into method label WECB parent_label H_EC remove_entry;
into device label H_EC insert
begin
Method (WE1B, 2, NotSerialized)\n
{\n
OperationRegion(ERAM, EmbeddedControl, Arg0, 1)\n
Field(ERAM, ByteAcc, NoLock, Preserve) { BYTE, 8 }\n
Store(Arg1, BYTE)\n
}\n
Method (WECB, 3, Serialized)\n
// Arg0 - offset in bytes from zero-based EC\n
// Arg1 - size of buffer in bits\n
// Arg2 - value to write\n
{\n
ShiftRight(Add(Arg1,7), 3, Arg1)\n
Name(TEMP, Buffer(Arg1) { })\n
Store(Arg2, TEMP)\n
Add(Arg0, Arg1, Arg1)\n
Store(0, Local0)\n
While (LLess(Arg0, Arg1))\n
{\n
WE1B(Arg0, DerefOf(Index(TEMP, Local0)))\n
Increment(Arg0)\n
Increment(Local0)\n
}\n
}\n
end;

假设写入 BCT0 的代码是:

1
Store(Local0, BCT0)

在这种情况下,不能用对 RECB 的调用来代替对 BCT0 的访问。因为这是写操作,而不是读操作。必须使用上面新创建的函数 WECB。

1
WECB(0x30,128, Local0)

WECB 函数的前两个参数与 RECB 相同(EC字段的偏移量和大小)。第三个参数(Arg2)是应写入 EC 字段的值。在这个例子中,是从 Local0 读取数据写入到第一个参数指定的偏移量里。

Store 并不是唯一可以执行写操作的 AML 操作码。Store 也不是唯一可以执行读操作的 AML 操作码。 例如 Add 操作码:

1
Add(X, Y, Z)

上面的示例从X读取,从Y读取,执行加法…并将结果写入Z。

当不确定 AML 操作码的用途时,请阅读 ACPI 规范。在那里有完整的文档,但不在本文讨论范围之内。

文字开头给出的 github 仓库是示例和学习的良好来源。仓库中的现有修补程序中有许多 WECB/RECB 示例。

充电/放电状态的逻辑错误(充电器检测)

某些 DSDT 存在逻辑错误,在这种情况下,容量达到 100%(电池充满电)时,_BST 返回了错误的状态。这主要影响某些华硕笔记本电脑,但也影响其他一些笔记本电脑。

这是解决此问题的补丁程序:

1
2
3
4
5
6
7
8
9
10
into method label FBST code_regex If\s\(CHGS\s\(Zero\)\)[\s]+\{[\s]+Store\s\(0x02,\sLocal0\)[\s]+\}[\s]+Else[\s]+\{[\s]+Store\s\(One,\sLocal0\)[\s]+\} replaceall_matched begin
If (CHGS (Zero))\n
{\n
Store (0x02, Local0)\n
}\n
Else\n
{\n
Store (Zero, Local0)\n
}
end;

错误报告

下载 patchmatic: https://bitbucket.org/RehabMan/os-x-maciasl-patchmatic/downloads/RehabMan-patchmatic-2015-0107.zip

从 zip 中解压出 ‘patchmatic’ 二进制文件. 复制到 /usr/bin, 即最终的文件位置是:/usr/bin/patchmatic.

在终端中:

1
2
3
4
if [ -d ~/Downloads/RehabMan ]; then rm -R ~/Downloads/RehabMan; fi
mkdir ~/Downloads/RehabMan
cd ~/Downloads/RehabMan
patchmatic -extract

注意:如果使用复制/粘贴而不是手动键入命令,会更容易。

~/Downloads/RehabMan 目录打包成 zip 文件。

另外将 ioreg 也打包成 zip:http://www.tonymacx86.com/audio/58368-guide-how-make-copy-ioreg.html. 注意要用帖子中提到的 IORegistryExplorer v2.1!不要使用其他版本的 IORegistryExplorer.app

还有以下命令的输出:

1
2
3
kextstat|grep -y acpiplat
kextstat|grep -y appleintelcpu
kextstat|grep -y applelpc

还有打包成 zip 的 EFI/Clover 目录(在打包前先在 Clover 界面按一下 F4)。注意要删除 ‘themes’ 目录,尤其是如果你安装了很多主题,还有只需要提供 EFI/Clover 而不是整个 EFI 目录。

还有以下命令的输出:

1
sudo touch /System/Library/Extensions && sudo kextcache -u /

再把上面提到的所有内容打包成一个大的 zip 文件并发布到帖子里。不要使用外部链接,使用编辑帖子时的上传文件功能。

贡献

如果你确实完成了给你的电池方法打补丁,建议你将你的补丁和你的电脑信息共享出来,让其他与你是同样配置的人也能使用你的补丁,我可以将你的补丁程序添加到上面提到的 github 仓库中。请同时提供包含补丁和本机 DSDT 的文本文件(以便我能够根据本机 DSDT 查看补丁程序)。只有可以将补丁应用到本机 DSDT上时,我才会将这个补丁添加到仓库中。

译者注:示例 DSDT 文件请从原文中下载。