黑苹果为小白设计的电池教程(DSDT)

5,711次阅读
没有评论

前言

在我们装黑苹果的过程中,电池显示成了头疼的问题,这个教程会尽量写的简单,只要你认真,你绝对看得懂!
此教程整理,修改,借鉴于:http://bbs.pcbeta.com/viewthread-1751487-1-1.html
对其进行了完善,以及一些有问题的地方进行了修改。

初步了解

实现原理 : 由于苹果无法使用 ACPI EC 中超过 8 位的寄存器(又叫 EC 缓冲区,Embedded Controller Buffer),我们需要利用 Hotpatch 的原理更名涉及到 EC 的 Method 使其失效并在新建的 SSDT 补丁中重新定义它们,使 macOS 能够通过 SMC 电池驱动正确识别电池 EC 信息。

 

好了,我觉得你应该得有个 可以用的 DSDT吧,如果没有请去提取自己的 DSDT 并反编译,排好错。具体见群文件的教程。

首先打开我们的 DSDT,搜索(alt+F4)Embeddedcontrol

黑苹果为小白设计的电池教程(DSDT)

OperationRegion 名称,此为 EC 操作区 的名称,一般名称为 ERAM、ECF2、ECF3、ECOR 等,并且有的机器可能不止一个

我们找到了这里,仔细观察,发现它在 EC0 控制器下,具体路径是_SB.PCI0.LPCB.EC0。当然每个人的可能不一样,最后的 EC0,还可能是 ECDV、EC、H_EC。

这里我们主要关注 Field 里的东西,就是那一堆四个字母的东西。在这一堆东西中,我们只要注意 8 位以上 的就行(就是右边的数字)。因为电池驱动无法处理 8 位以上的字节,所以就需要我们手动来处理来。

我们需要用到的工具:计算器(Mac 自带)Maciasl新建一个 txt 文件

 

打开 txt 文件,我们先把一下代码复制进去(我会把这个做成样例放在 群文件

处理方法补丁如下:




 

# created by GZxioabai

 

# add method B1B2

into method label B1B2 remove_entry;

into definitionblock code_regex . insert

begin

Method (B1B2, 2, NotSerialized)\n

{\n

Return(Or(Arg0, ShiftLeft(Arg1, 8)))\n

}\n

end;

 

# add method B1B4

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;

 

# add utility methods to read/write buffers from/to \_SB.PCI0.LPCB.EC0

into method label RE1B parent_label \_SB.PCI0.LPCB.EC0 remove_entry;

into method label RECB parent_label \_SB.PCI0.LPCB.EC0 remove_entry;

into Device label EC0 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 \_SB.PCI0.LPCB.EC0\n

// Arg1 - size of buffer in bits\n

{\n

ShiftRight(Arg1, 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;

 

into method label WE1B parent_label \_SB.PCI0.LPCB.EC0 remove_entry;

into method label WECB parent_label \_SB.PCI0.LPCB.EC0 remove_entry;

into Device label EC0 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(Arg1, 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;

 


以上的这些东西是同用的处理方法,包括 B1B2(16 字节处理),B1B4(32 字节处理),WECB 和 RECB(这两个是处理 32 字节以上的)

16 位处理方法

接下来,我们来讲讲 16 位如何处理。

 

比如我们在 Field 下找到的这个 16 位的 BADC,我们需要将它拆分掉,拆成来 两个 8 字节,这样就能被电池驱动处理了。

读取操作:

我们还是先来解释一下吧,什么是读取什么是写入?在 DSDT 中常见的是下面两种语句。

第一种语句(老):Store(BADC,ENC0)

在这里,Store 语句中,BADC的操作,而 ENC0 的操作,解释一下,就是将 BADC 写入到 ENC0,所以你可几个口诀就是“ 左读右写

第二种语句(新):ENC0 = BADC

在这里,就刚好相反了,这里没有了 Store,但意思还是 将 BADC 写入到 ENC0,所以 BADC 还是 ENC0还是

写入操作:

Store(FB4,BADC)

在这里,Store 语句中,FB4的操作,而 BADC 的操作,解释一下,就是 将 BADC 写入到 ENC0,所以你可几个口诀就是“左读右写

那么其实很好理解了 BADC = FB4 这个就跟上面提到的反一下

了解了这些那么你可以继续接下来的拆分工作了。

 

Field(声明字段)下处理补丁:into Device label EC0 code_regex BADC,\s+16, replace_matched begin DCA0,8,DCA1,8, end;

我们先来理解一下这个,into:针对

Device label:关于这个设备范围里

EC0: 设备的名称

code_regex:匹配搜索

BADC,\s+16:被搜索的代码,\s+16 表示 16 字节

replace_matched:匹配替换

begin DCA0,8,DCA1,8, end:从什么什么开始,到什么什么结束,这里的意思就是,用于替换的是“DCA0,8,DCA1,8,”

那么整句话的意思就是,在 设备 EC0 的范围内搜索 16 字节的 BADC,如果有,就替换为“DCA0,8,DCA1,8,”

我们在来表示成一个处理结果:BADC,16,—–>DCA0,8,DCA1,8,

当然这只是在声明字段中进行拆分处理,我们还要在 BADC被调用的地方 进行处理。

我们首先需要查找一下 BADC 在哪些地方被调用。(重要提醒:没被调用的其实不需要拆分!意思是你根本不用去管它!

 

被调用的字段(一般在 Method 下)那里,对字段进行拆分:

的处理补丁:into method label SMTF code_regex BADC replaceall_matched begin B1B2(DCA0,DCA1) end;

解释:into method label SMTF:针对 Method 为 SMTF 的这个范围内

code_regex:匹配(搜索)

BADC:被搜索的字段

replace_matched:替换匹配

begin B1B2(DCA0,DCA1) end:这是被替换的内容

那么总的意思就是,在 method 为 SMTF 这个范围里面,搜索“BADC,\s+16”,,如果有,就把它替换为“DCA0,8,DCA1,8,”。

那么最后的处理结果是:

未处理前:




Method (SMTF, 1, NotSerialized)

{If (LEqual (Arg0, Zero))

{Return (BADC)

}

 

If (LEqual (Arg0, One))

{Return (Zero)

}

 

Return (Zero)

}



打了补丁之后:Method (SMTF, 1, NotSerialized)

{If (LEqual (Arg0, Zero))

{Return (B1B2 (DCA0, DCA1))

}

 

If (LEqual (Arg0, One))

{Return (Zero)

}

 

Return (Zero)

}


当然啦,

这仅仅是 BADC 如果是 读取 的时候的处理,那要是碰到 写入 的时候,我们就要像下面这样处理,不能使用 B1B2 的方法了

比如:

Store (Arg0, BADC)(BADC 是 16 位的情况)

需要改为:

Store (ShiftRight(Arg0,8),DCA1)(DCA1 是 16 位拆分后的第二个)

Store (Arg0,DCA0)(DCA0 是 16 位拆分后的第一个)

那么补丁,我们就可以这样写:

into method label SMRW code_regex Store\s\(Arg3,\sBADC\) replaceall_matched begin Store(ShiftRight(Arg3,8),DCA1)\nStore(Arg3, DCA0) end;

其中这段文字中的 \s 代表的是一个空格,\n 代表的是换行,也就是回车,主要的是在搜索那里,需要注意符号转义,在任何符号前都要加一个反斜杠转义,也就是加一个 \

 

那最后的处理结果是:

未处理:




Store (Arg0, BADC)


打了补丁后:




Store (ShiftRight(Arg0,8),DCA1)

Store (Arg0,DCA0)


 32 字节处理方法

32 位字段的处理方法其实跟 16 位一样,用到的是 B1B4,区别就是,16 位拆除 2 个,32 拆除 4 个

在 Field 里查找 32 位的,这里我们也是举一个例子,比如B1CH

补丁如下:

into Device label EC0 code_regex B1CH,\s+16, replace_matched begin CH10,8,CH11,8,CH12,CH13 end;

处理结果为:

B1CH,32, ——> BC0H,8,BC1H,8,BC2H,8,BC3H,8,

我们可以发现,这个跟 16 位的差不多,就是后面 多拆 2 个,那就不用多废话解释了。

我们直接讲在被调用的地方的处理(32 字节基本不会有写入操作,也从未出现过

补丁如下:


into method label _BIF code_regex B1CH replaceall_matched begin B1B4(CH10,CH11,CH12,CH13) end;

那这个也就不解释了,差不多的意思。其中 B1B432 位 处理方法

处理结果:




Method (_BIF, 0, NotSerialized)

{Store (B1CH, IFCH) // 未处理前

}


打了补丁后:




Method (_BIF, 0, NotSerialized)

{Store (B1B4 (BC0H, BC1H, BC2H, BC3H), IFCH) // 把被调用 B1CH 两处拆分为 4 个字节

}


偏移量计算

到了 32 位以上的字段处理,我们会使用到 RECB(读)WECB(写)两个处理方法

我先给你看两个例子:

RECB(0x98, 64)

WECB (0x1C, 256, FB4)

我们来解释一下它们的组成部分,RECB(偏移量, 字段长度)WECB(偏移量, 字段长度, 未处理前的前参数)

字段长度很好理解,64 位就是 64,128 位就是 128,256 位就是 256

WECB中的 未处理前的前参数,我们举个例子好理解一点

比如:

Store (FB4, SMD0)

SMD0256 位的需要处理的字段,在这里是 写入,那么它的前参数,顾名思义就是前面那个FB4

那么其实,最主要的问题是 偏移量 了。

举例 1:




Offset (0x04),(基地址)CMCM, 8, //0x04

CMD1, 8, //0x05

CMD2, 8, //0x06

CMD3, 8, //0x07

Offset (0x18),

Offset (0x19),(基地址)SMST, 8, //0x19

MBMN, 80, //0x1A

MBPN, 96, //0x24

GPB1, 8, //0x30

GPB2, 8, //0x31 

GPB3, 8, //0x32 

GPB4, 8, //0x33 


我们看这里的,MBMN是需要处理的 80 位字段,它的偏移量的计算就要涉及到它上面的 基地址 ,我们看到了那个基地址是0x19,我们还可以发现它前面有个 8 位的 SMST,我们将 8 除以 8,得到 1,再把 0x19 加上这个 1,最后得到了0x1A,那么下面那个 MBPN 的偏移量怎么算呢,就是 将前面的都加起来除以 8 ,再加上 基地址 ,就是 8 加上 80 得到 88,除以 8,等于 11,转换为 16 进制就是 B,0x19 加上 B,等于 0x24.( 注意的是,在除以 8 后的数字,一定要转换为 16 进制,再加上基地址!

举例二:



Offset (0x53), //(基地址)B0TP, 16, // 从基地址起,为 0x53 
B0VL, 16, //16,为 2 个字节;计算:上一个的起始地址 0x53+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x55
B0CR, 16, //16,为 2 个字节;计算:上一个的起始地址 0x55+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x57
B0AC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x57+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x59
B0ME, 16, //16,为 2 个字节;计算:上一个的起始地址 0x59+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x5b
B0RS, 16, //16,为 2 个字节;计算:上一个的起始地址 0x5b+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x5d
B0RC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x5d+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x5f
B0FC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x5f+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x61
B0MC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x61+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x63
B0MV, 16, //16,为 2 个字节;计算:上一个的起始地址 0x63+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x65
B0ST, 16, //16,为 2 个字节;计算:上一个的起始地址 0x65+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x67
B0CC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x67+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x69
B0DC, 16, //16,为 2 个字节;计算:上一个的起始地址 0x69+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x6b
B0DV, 16, //16,为 2 个字节;计算:上一个的起始地址 0x6b+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x6d
B0SI, 16, //16,为 2 个字节;计算:上一个的起始地址 0x6d+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x6f
B0SN, 32, //32,为 4 个字节;计算:上一个的起始地址 0x6f+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x71 
B0MN, 96, //96,为 12 个字节 计算:上一个的起始地址 0x71+0x4(上一个的 32 位占了 4 个字节,10 转为 16 进制为 0x4)值为 0x75
B0DN, 64, // 64,为 8 个字节;计算:上一个的起始地址 0x75+0xc(上一个的 96 位占了 12 个字节,10 转为 16 进制为 0xc)值为 0x81
B0CM, 48, // 计算:上一个的起始地址 0x81+0x8(64 位占了 8 个字节,10 转为 16 进制为 0x8)值为 0x89

这里我就不说明了,自己看右边的注释理解一下吧。
举例 3:



Offset (0x5D), //(基地址)ENIB, 16, // 16,为 2 个字节;从基地址起,为 0x5D
ENDD, 8, //8,为 1 个字节;计算:上一个的起始地址 0x5D+0x2(上一个的 16 位占了 2 个字节,10 转为 16 进制为 0x2)值为 0x5F
SMPR, 8, //8,为 1 个字节;计算:上一个的起始地址 0x5F+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x60
SMST, 8, //8,为 1 个字节;计算:上一个的起始地址 0x60+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x61
SMAD, 8, //8,为 1 个字节;计算:上一个的起始地址 0x61+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x62
SMCM, 8, //8,为 1 个字节;计算:上一个的起始地址 0x62+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x63
SMD0, 256, //256,为 32 个字节;计算:上一个的起始地址 0x63+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x64
BCNT, 8, //8,为 1 个字节;计算:上一个的起始地址 0x64+0x20(上一个的 256 位占了 32 个字节,10 转为 16 进制为 0x20)值为 0x84
SMAA, 24, //8,为 1 个字节;计算:上一个的起始地址 0x84+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x85

举例 4 最为简单:




Field (ERAM, ByteAcc, NoLock, Preserve)

{Offset (0x04),

FLD0, 64 // 64,为 8 个字节;从基地址起,为 0x04(偏移量)

}

 

Field (ERAM, ByteAcc, NoLock, Preserve)

{Offset (0x04),

FLD1, 128 // 128,为 16 个字节;从基地址起,为 0x04(偏移量)

}

 

Field (ERAM, ByteAcc, NoLock, Preserve)

{Offset (0x04),

FLD2, 192 // 192,为 24 个字节;从基地址起,为 0x04(偏移量)

}

 

Field (ERAM, ByteAcc, NoLock, Preserve)

{Offset (0x04),

FLD3, 256 // 256,为 32 个字节;从基地址起,为 0x04(偏移量)

}


举例五 特殊:




OperationRegion (SMBX, EmbeddedControl, 0x18, 0x28) // 第三个值是起始地址

Field (SMBX, ByteAcc, NoLock, Preserve)

{PRTC, 8, //8,为 1 个字节;上面第三个值是起始地址 0x18

SSTS, 5, // 计算:上一个的起始地址 0x18+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x19

, 1,

ALFG, 1,

CDFG, 1, // 上面 5+1+1+ 1 才凑够 8 位(1 字节)

ADDR, 8, //8,为 1 个字节;计算:上一个的起始地址 0x19+0x1(上面 5+1+1+ 1 才凑够 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x19 0x1A

CMDB, 8, //8,为 1 个字节;计算:上一个的起始地址 0x1A+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x1B 

BDAT, 256, //256,为 32 个字节;计算:上一个的起始地址 0x1B+0x1(上一个的 8 位占了 1 个字节,10 转为 16 进制为 0x1)值为 0x1C

BCNT, 8,

, 1,

ALAD, 7,

ALD0, 8,

ALD1, 8

 

}


32 位以上字段的处理(包括 64,128,256 等)

在 Field 下,我们需要对其进行 重命名 使其失效。

补丁如下:

into Device label EC0 code_regex (SMD0,)\s+(256) replace_matched begin SMDX,%2,//%1%2 end;

这里需要注意的是 要打括号 !,还有后面的SMDX 是重命名后的结果,%2,//%1%2这个也是要加上的!

接下来在被调用的地方进行处理:

读取调用

Store (SMD0, FB4)

我们要用到 RECB,补丁如下:

into method label MHPF code_regex SMD0 replaceall_matched begin RECB(0x1C, 256) end;

处理结果为:Store (SMD0, FB4) —> Store (RECB (0x1C, 0x0100), FB4)

写入调用

Store (FB4, SMD0)

我们要用到 WECB,补丁如下:

into method label MHPF code_regex Store\s\(FB4,\sSMD0\) replaceall_matched begin WECB(0x1C,256,FB4) end;

值得注意的是,我们这边是 将整个 Store 语句进行了替换 ,这也是WECB 处理的不同之处。

处理结果:Store (FB4, SMD0) —> WECB (0x1C, 256, FB4)

 

# Mutex 确认,最后检查

确保 DSDT 里的 Mutex 都是0x00,不然可能会出现电量显示 0% 的情况。

在 DSDT 里搜索Mutex,如果有的不是 0x00,你就自己手动改成 0x00。

补充

当电池有时能正常显示电量,有时不能会出现一个小叉,则可能是多个电池的位置导致的,如图有两个位置,分别为“BAT0”和“BAT1”,我们需要禁用掉“BAT1”这个位置,以达到正常读取电量

 
评论(没有评论)
载入中...