双屏笔记本在 macOS 下的触控板驱动研究


实际上关于黑苹果的文章本博客不打算再记录的了,之前还单独开了个子域名用于记录黑苹果:国光的黑苹果安装教程,但是呢由于这个触控板真的耗费了我太多精力了,最终搞定了还是比较有成就感的,所以就单独来博客写文章记录一下,而且也可能会帮助到其他看到本文的网友。

入手背景

4 个月前逛 reddit 论坛被一个帖子给种草了,原帖子地址为:https://www.reddit.com/r/hackintosh/comments/pdg47d/work_in_progress_20/

居然发现触控板是一个触控屏的笔记本,而且居然也成功吃上了黑苹果,这让我大受震撼,内心开始思考:


“ 普通笔记本黑苹果的触控板体验一般都不太行,毕竟硬件限制摆在那儿,完全和 Macbook 的玻璃触控板没法比,但是这种双屏设备的触控板是一个触控屏,如果可以黑苹果成功的话,那体验应该也无限接近于白苹果的触控板了吧,233333333333 ”


以上想法纯属 YY,后面我一时冲动去咸鱼上花了高昂的价钱入手了 2 个双屏笔记本:

但是却忽略了原贴评论里面说的「目前这个触控板不支持苹果的多指手势,只能识别到 1 个手指」。

呜呜呜呜,所以这几个月时间内,国光我一直在研究如何驱动这个触控板的多指手势,这个过程真的非常艰辛和漫长,期间甚至还买了苹果官方的妙控板:

也在 B 站水了视频,不过视频里面还是单指触控的版本:史上最骚的黑苹果笔记本诞生了?

最近写完这篇文章之后,准备系统做个视频来记录一下了 :)

发现问题

因为触控板走得是 I2C 协议的,所以肯定使用经典的 https://github.com/VoodooI2C/VoodooI2C 项目来驱动了,使用官方的 kexts,按照如下顺序加载:

虽然设置里面的触控板看上去很完美,但是使用 FingerMgmt 检测只实际上只可以检测到 1 个手指:

对于笔记本来说,这个最为致命,完全没法当做生产力了。

备注:实际上 VoodooI2C 驱动默认情况下是无法使用这个触控板的,连最基础的单指功能都无法实现,还得改下 VoodooI2CHID.kext/Contents/Info.plist 这个文件才可以,不过由于国光我目前已经解决多指问题了,所以本文中就不再赘述这如何驱动单指的情况了。

信息收集

工欲善其事,必先利其器,所以要做好一件事,准备工作是非常重要的。

硬件信息

先在 Windows 下对触控板信息进行一个简短的整理,使用的是标准的符合微软 I2C 协议触控板:

在 macOS 下使用IORegistryExplorer也是可识别到触控板的型号为:GDX1515

将以上可能有用的信息整理出来如下:

  • Windows 下这种二合一触控板叫法:ScreenPad

  • 触控板型号:GDX1515

  • 触控板的屏幕型号:ScreenXpert

  • IRQ0x0000006D (109)

  • APIC Pin6d(109)

  • 设备实例路径ACPI\GDX1515\1

  • 硬件 Id

    • ACPI\VEN_GDX&DEV_1515
    • ACPI\GDX1515
    • *GDX1515
  • BIOS 设备名称\_SB.PCI0.I2C1.ETPD

  • 位置路径

    • ACPI(_SB)#ACPI(PCI0)#(I2C1)#ACPI(ETPD)
    • ACPI(_SB)#ACPI(USBX)#(I2C1)#ACPI(ETPD)

寻找线索

知道触控板的型号的话,首先先看看历史有没有人驱动了这个触控板,如果有的话,我们直接白嫖的话岂不美哉?

谷歌一下,发现最早这个问题 19 年就有人提问过了,帖子地址:https://www.tonymacx86.com/threads/mojave-10-14-6-on-asus-vivobook-s532fa.284231/

我根据线索搜索下了下他设备的型号,发现果然也是一个双屏笔记本:

后面我也在 VoodooI2C 的官方聊天室 里也发现从 19 年至今,很多人都遇到这个触控板无法完美驱动的问题,VoodooI2C 开源工程目前也不活跃,所以这个问题一直都没有得到解决,国光我从 9 月份加入官方聊天室以来,经常打着时差和国外的开发者沟通交流,期间的过程真的非常煎熬和痛苦:

凌晨5点多还在编译项目

初步结论

目前这种触控板可以下结论为无解了,理论上想要驱动的话,只能尝试自己去改 VoodooI2C 源码,或者直接移植 Linux 驱动,当然移植驱动是一个大工程,不到迫不得已不要尝试,凭我目前的水平,自学 Linux 驱动移植至少也得 3 个月,啊这:

编译项目

  1. 魔改 VoodooI2C 源码
  2. 移植 Linux 驱动

目前这两种解决方法中只有前者可以勉强尝试一下,移植 Linux 驱动是一个大工程,不到万不得已不会去尝试的。

魔改 VoodooI2C 源码前首先得会编译官方的原始项目吧,如果编译都通过不了,谈何去魔改?所以这一小节主要记录如何使用 Xcode 编译 VoodooI2C 触控板项目的源码。

克隆源码

VoodooI2C 项目所依赖的模块属于不同的仓库,所以 git clone 的时候需要使用 recursive 参数一次性克隆下载完:

# 本次存放的文件夹路径仅供参考
$ pwd
/Users/bytedance/Desktop/XCode

$ cd ~/Desktop/XCode

$ git clone --recursive https://github.com/VoodooI2C/VoodooI2C.git

打开项目

使用 Xcode 打开VoodooI2C.xcworkspace文件:

打开项目肯定不是一帆风顺的,我们会看到许多 issue:

这里实际上我们不用管它,全部默认就好了,不要手动去点击「Perform Changes」哦。

IOKit 缺失

点击「Product」-「Build」先编译一下,发现报错了,提示:'IOKit/IOLib.h' file not found

这个地方一开始卡了我很久,后面发现在项目的「Build Settings」里面的「Library Search Paths」里面的 MacKernelSDK这个文件夹我并没有:

所以才会导致找不到 IOKit 的库文件,解决方法就是手动去补充完善 MacKernelSDK 的文件夹。

acidanthera 大佬维护的 MacKernelSDK 的一个开源项目地址为:https://github.com/acidanthera/MacKernelSDK

# 注意我当前操作的路径
$ pwd                                                           ✔
/Users/bytedance/Desktop/XCode/VoodooI2C

# 直接下载这个项目
$ git clone https://github.com/acidanthera/MacKernelSDK.git

此时再编译会发现之前的'IOKit/IOLib.h' file not found 报错消失了,这个问题得到了解决。

VoodooInputMultitouch 缺失

解决完上面一个小问题后,会发现又出了新的报错了:

'VoodooInputMultitouch/VoodooInputTransducer.h' file not found

不要慌,问题很大,这是因为项目里面缺少了对应的文件,也需要我们手动去补充一下。比较巧合的是,这个项目也是由acidanthera 大佬维护的 ,地址为:https://github.com/acidanthera/VoodooInput

VoodooInputMultitouch 文件拷贝到 VoodooI2C/Multitouch\ Support/Native 目录下:

此时再编译应该不会报错了。

cldoc 或者 cpplint 缺失

不过总有人会各种不顺,还可能会遇到 xargs: cldoc: No such file or directory 或者 cpplint: command not found这类报错,国光我这里忘记怎么解决的了,好像是直接安装 Python 的库来解决的:

sudo easy_install cldoc
sudo easy_install cpplint

具体细节忘记了,但是这块不复杂,既然都研究到 Xcode 编译这一步了,所以这个问题应该也难不倒大家的吧。

编译项目

至此所有的报错都解决了,下面直接来编译生成 kexts 吧,首先点击 build编译一下,很顺利:

然后点击「Product」接着点击「Archive」打包项目:

然后会弹出应该窗口,选择我们打包的最新版本,选择「Distribute Content」

后面默认选择「Build Products」即可:

最后桌面上就躺好了我们编译好的 kexts 文件了:

坑点记录

我们编译的 VoodooI2C.kext 的 Pluglns 不完整,里面是两个替身:

解决方法可以手动复制目录外面的 kext 进去,或者直接使用官方编译好的 VoodooI2C.kext,不过这个问题不大,目前我手上的触控板主要关注 VoodooI2CHID.kext 这个文件就 OK 了。

问题排查

既然可以编译官方项目了,那么下面得开始修改代码了,那么对于国光我这种信息安全从业者来说,直接改底层驱动代码的成功率几乎为 0,所以我在官方的项目提交了一个 issus:

Dual-screen devices with integrated trackpad and screen do not support multi-finger gestures(GDX1515)

这个 issus 一直没啥进展,官方也表示双屏设备很棘手,不会考虑支持,本以为就这样凉了。但是 3 个月过去了,issus 里面来了一个老哥 gvkt ,他和我一样也遇到这个问题,然后我就经常和他在 issus 里面交流,终于有一天找到了一个非常关键的原因:

然后我便如醍醐灌顶一般,顿悟了,由于这个 issus 比较长,我下面简单来概括一些这么长时间的发现吧:

  1. 这个触控板在 Linux 上有时候也无法使用多指,关于这个 BUG 的细节可以参考:https://bugzilla.kernel.org/show_bug.cgi?id=204991
    • 触控板开机默认以传统鼠标模式运行(单指触控),然后通过发送 I2C SET_OR_SEND_REPORT 命令进入 PTP (精密触控板 多指手势)模式
    • 从 I2C SET_POWER ON 到 SET_OR_SEND_REPORT 命令的间隔必须大于 60ms,否则可能进入 PTP 模式失败
    • 起初我也以为是这个问题,所以在 enterPrecisionTouchpadMode() 函数之前增加 IOSleep 延迟,编译后发现并没有解决问题,所以这个不是根本的问题
  2. 从触控板的解析报告中可以得出,在 macOS 下符合标准的 I2C 协议,也识别到多指了
    • 理论上这个触控板是可以驱动的,但是由于某些代码冲突导致最后只有 1个手指工作
  3. 在 VoodooI2CMultitouchHIDEventDriver 的代码中会忽略额外的手指数据
    • 这个问题比较关键,相当于是触控屏和触控板代码冲突导致最后只有 1 个手指工作

尝试魔改

修改代码

既然知道问题代码所在了,那么下面尝试更改一下看看吧,主要修改了这个文件:https : //github.com/VoodooI2C/VoodooI2CHID/blob/master/VoodooI2CHID/VoodooI2CMultitouchHIDEventDriver.cpp

我的解决思路也比较简单,搜索 kHIDUsage_Dig_TouchScreen 关键词,删除相关代码即可。主要修改的部分如下。

删除 619 行的判断条件:

if (element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_Pen)
    || element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_TouchScreen)
    || element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_TouchPad)
    || element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_DeviceConfiguration)
   )

修改为:

if (element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_Pen)
|| element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_TouchPad)
|| element->conformsTo(kHIDPage_Digitizer, kHIDUsage_Dig_DeviceConfiguration)
)

没错这样就 OK 了,果然找到关键点的话,修改起来并没有很复杂,接着直接编译生成 VoodooI2CHID.kext ,完美!

更改 info.plist

不过虽然代码里面删除了触屏的部分代码了,但是最终生成的 VoodooI2CHID.kext/Contents/info.plist 文件里面的触控部分也要删掉,否则依然无法成功驱动触控板,主要删除下面这部分代码即可:

<key>VoodooI2CHIDDevice Touchscreen HID Event Driver</key>
		<dict>
      ...
		</dict>

即删除下面的这部分:

最终效果

最终这种双屏触控板完美支持多指手势了,体验上和 Macbook 真的有的一拼,真的如丝般顺滑:

思考拓展

大家也发现了,最终驱动本触控板的主要原理是删除了触控屏的相关代码,虽然在本次的笔记本上工作比较良好,但是如果存在像下面这种多屏幕的设备该怎么让触控屏和触控板完美工作呢(这个本本下面这个触控屏也是 GDX1515):

虽然目前 VoodooI2C 官方没有解决这个问题,但是 gvkt 老哥单独开了个分支用于解决这类问题,详情可见他的项目:

https://github.com/gvkt/VoodooI2CHID

下面是他具体修改的 Commit 记录,感兴趣的师傅可去研究手动编译下看看效果:

https://github.com/gvkt/VoodooI2CHID/commit/d305a0e0913736d88a43d5c3c18ec7b8ec728c75

触控板的工作模式

目前为止这个触控板就完美了吗?当然不是,当前触控板工作在轮询模式(polling)下,比较完美的情况是让触控板工作在中断模式(interrupts)下,关于轮询和中断可以参考下面几个概念:

  • APIC 中断
    • macOS 使用的中断模式,功能完美,极少数设备支持
    • 只有 APIC Pin 值小于 2F (47)的时候才支持
  • GPIO 中断
    • Windows 系统下大多使用的中断模式
    • 仅次于 APIC 中断,比较高效,但是需要自己更改定制 SSDT
  • 轮询
    • 比较低效的模式
    • 但是兼容性比较好,大部分触控板都适用
    • 容易出现指针漂移等不灵敏的 BUG

那我们目前的触控板工作在啥模式情况下呢?通过前面的信息收集,我们知道触控板的 APIC Pin 值为:

  • IRQ0x0000006D (109)
  • APIC Pin6d(109)

很明显,这个 GDX1515 触控板是不支持 APIC 中断的。那么如何判断当前触控板的工作模式呢?目前有下面 3 种方法

查看 dmesg 日志

顺利的情况下使用 dmesg 命令可以直接根据触控板型号的关键词可以搜索到中断还是轮询的工作状态:

但是黑苹果总不会一帆风顺的,正如你所见,dmesg 查看的日志结果为空的,这里面可能有两种原因:

  1. macOS 12.0 系统限制了 dmesg 查看的内容(可能性比较高)
  2. 自己编译的 VoodooI2C 是未签名的,无法正常使用 dmesg 查看日志

以上两种原因,纯属国光猜测,不管如何这里需要我们手动加载 kexts 来查看日志。

关闭 SIP

很多 OC 的配置默认是开启 SIP 的,因为我们要加载未签名的 Kexts,所以需要在开机选择系统的界面手动切换下 SIP 状态:

如果你的 OC 没有这个选项,是因为没有勾选这个配置开关,具体细节可以参考我的这部分文章:国光的黑苹果安装教程:手把手教你配置 OpenCore

OC 里面禁用 Kexts

通过 OpenCore 注入的 kexts 是无法正常看到 dmesg 日志的,所以我们需要手动禁用下相关的 kexts:

手动加载 Kexts

VoodooI2C.kextVoodooI2CHID.kext 拷贝到桌面上,然后执行以下命令:

# 修改所有者
sudo chown -R root:wheel VoodooI2C*

# 修改权限
sudo chmod -R 755 root:wheel VoodooI2C*

# 加载 kexts
sudo kextload -v VoodooI2C*

当然第一次加载 kexts 得在设置的「安全性与隐私」里面同意下权限,然后重启下电脑才可以成功加载:

经过上述折腾,最终查看的效果如下:

可以看到通过 dmesg 日志可以发现此 GDX1515 触控板工作在 polling 模式下,即轮询模式。

关于日志的查看细节,下面会单独开个子目录详细介绍。

使用 IORegistryExplore

上面的方法可能有点繁琐,实际上使用IORegistryExplorer也是直接看到当前触控板的工作状态的,因为在 Windows 的设备管理器下我的触控板的位置路径为:

  • ACPI(_SB)#ACPI(PCI0)#(I2C1)#ACPI(ETPD)
  • ACPI(_SB)#ACPI(USBX)#(I2C1)#ACPI(ETPD)

所以搜索 ETPD 可以看到我们触控板的详细信息,一般会搜索出两个结果,下面是第一个结果:

不过第一个结果没有参考价值,我们一般关注搜索出来的第 2 个结果:

7

像上面这个图就是一个典型的没有工作在中断模式的情况。

这个时候肯定有网友会问,如果是 GPIO 中断模式的话,那这里应该长啥样子呢?

2333 这个问题问的不错,国光请教了 Bat.bat 大佬,下面是大佬的原话:


代码里走到 GPIO 会有个 set property 把 IRQ 和 Pin 写到 ioreg 下,所以使用 IORegistryExplorer 查看的话,关注是否有这两个新增的属性即可。


说到这里可能还有网友不是很明白,下面国光我帖一个处于 GPIO 中断模式下的 IORegistryExplorer 截图,大家应该就懂了:

使用 GenI2C(易误报)

除了上述两种方法,使用GenI2C 也是可以很方便的查看触控板的工作状态的:

不过作者貌似删掉了编译好的版本了,大家如果需要使用的话可以蓝奏云自取。

后面研究 GPIO 中断的时候发现,GenI2C 识别的工作模式 很容易误报,这也可能是作者删除项目的主要原因吧。

再后面发现,这个项目居然还是 Bat.bat 大佬写的:跟大狗子一起玩的,随便乱写的,当学 Swift 的。所以这个项目被作者删了,233333

新的风暴已经出现,怎么能够停滞不前? 既然知道当前的触控板不是处于 GPIO 中断模式的话,那么下面就来着手研究如何让触控板在中断模式下工作了。

定制修补 DSDT

提取 DSDT

因为制作触控板肯定是需要定制 SSDT 的,所以提取出主板原始的 DSDT 少不了。

提取 DSDT 的方法有很多,可以使用 Clover,Windows 下可以使用 AIDA64,macOS 下可以使用 DPCIManager ,直接打开点击左上角的「Extract DSDT」即可:

DSDT 排错

可以使用 OC 直接在 ACPI 里面加载 DSDT,也可以将 DSDT 里面触控板的相关代码单独提取出来,保存为 SSDT 文件。两个方法都可以,这里国光我是直接在 DSDT 这里修改的(简单粗暴)。

DSDT 排错的话这里要使用必备的 MaciASL 这个软件,直接点击「Compile」编译即可,如果没猜错的话肯定会有一堆报错:

这是因为每家主板的 DSDT 的 ACPI 规范不统一,所以我们得根据自己的编程经验去修改报错,这块不太好写文章描述出来,因为每个主板都不一样,国光我这里的报错还算比较容易理解,我直接删掉了这些Zero` 代码即可,23333(具体排错得根据对应报错情况来)

最终成功 0 errors 了:

DSDT 没问题的话就可以直接使用 OC 加载了:

寻找触控板代码

因为根据前面的信息收集,我们知道了触控板的路径为 ETPD,所以直接搜索 ETPD 即可找到触控板的代码,

发现左下角的路径也满足我们上面信息收集(ACPI(_SB)#ACPI(PCI0)#(I2C1)#ACPI(ETPD))的情况:

下面将触控板的代码部分贴出来:

Scope (_SB.PCI0.I2C1)
    {
        Device (ETPD)
        {
            Name (_ADR, One)  // _ADR: Address
            Name (ETPH, Package (0x01)
            {
                "ASUE1407"
            })
            Name (FTPH, Package (0x09)
            {
                "FTE1001", 
                "FTE1200", 
                "FTE1200", 
                "FTE1300", 
                "FTE1300", 
                "FTE1201", 
                "FTE1200", 
                "FTE1200", 
                "FTE1200"
            })
            Name (GTPH, Package (0x05)
            {
                "GDX1505", 
                "GDX1300", 
                "GDX1200", 
                "GDX1301", 
                "GDX1515"
            })
            Method (_HID, 0, NotSerialized)  // _HID: Hardware ID
            {
                If (And (TPDI, 0x04))
                {
                    Return (DerefOf (Index (ETPH, TPHI)))
                }

                If (And (TPDI, 0x10))
                {
                    Return (DerefOf (Index (FTPH, TPHI)))
                }

                If (And (TPDI, 0x40))
                {
                    Return (DerefOf (Index (GTPH, TPHI)))
                }

                Return ("ELAN1010")
            }

            Name (_CID, "PNP0C50")  // _CID: Compatible ID
            Name (_UID, One)  // _UID: Unique ID
            Name (_S0W, 0x03)  // _S0W: S0 Device Wake State
            Method (_DSM, 4, NotSerialized)  // _DSM: Device-Specific Method
            {
                If (LEqual (Arg0, ToUUID ("3cdff6f7-4267-4555-ad05-b30a3d8938de") /* HID I2C Device */))
                {
                    If (LEqual (Arg2, Zero))
                    {
                        If (LEqual (Arg1, One))
                        {
                            Return (Buffer (One)
                            {
                                 0x03                                           
                            })
                        }
                        Else
                        {
                            Return (Buffer (One)
                            {
                                 0x00                                           
                            })
                        }
                    }

                    If (LEqual (Arg2, One))
                    {
                        Return (One)
                    }
                }
                Else
                {
                    Return (Buffer (One)
                    {
                         0x00                                           
                    })
                }
            }

            Method (_STA, 0, NotSerialized)  // _STA: Status
            {
                If (LOr (LNotEqual (TPIF, One), LAnd (DSYN, One)))
                {
                    Return (Zero)
                }

                Return (0x0F)
            }

            Method (_CRS, 0, Serialized)  // _CRS: Current Resource Settings
            {
                Name (SBFI, ResourceTemplate ()
                {
                    I2cSerialBusV2 (0x0015, ControllerInitiated, 0x00061A80,
                        AddressingMode7Bit, "\\_SB.PCI0.I2C1",
                        0x00, ResourceConsumer, , Exclusive,
                        )
                    Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive, ,, )
                    {
                        0x0000006D,
                    }
                })
                Return (SBFI)
            }
        }
    }
}

GPIO Pinning 固定

国光这里只介绍如何定制 GPIO 中断,轮询和其他模式不在本文的范畴内。

寻找触控板关键代码

根据_CRS方法这个特征,我们可以很容易直找到触控板代码中类似如下的代码片段:

Method (_CRS, 0, Serialized)  // _CRS: Current Resource Settings
{
  Name (SBFI, ResourceTemplate ()
        {
          I2cSerialBusV2 (0x0015, ControllerInitiated, 0x00061A80,
                          AddressingMode7Bit, "\\_SB.PCI0.I2C1",
                          0x00, ResourceConsumer, , Exclusive,
                         )
            Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive, ,, )
          {
            0x0000006D,
          }
        })
    Return (SBFI)
}

重命名 SBFI

VoodooI2C 在以 GPIO 中断模式调用 DSDT 中触摸设备的_CRS 方法时,一律使用 SBFG 参数而不是 SBFI 参数。所以我们目前的触控板代码里面的 SBFI 变量是不满足要求的,我们先把 SBFI重命名为 SBFB,至于 SBFG 变量我们后面单独添加一下(如果你的 DSDT 里面没有的话)。

Method (_CRS, 0, Serialized)  // _CRS: Current Resource Settings
{
  Name (SBFB, ResourceTemplate ()
        {
          I2cSerialBusV2 (0x0015, ControllerInitiated, 0x00061A80,
                          AddressingMode7Bit, "\\_SB.PCI0.I2C1",
                          0x00, ResourceConsumer, , Exclusive,
                         )
  //         Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive, ,, )
          
  //        {
  //          0x0000006D,
  //        }
        })
    Return (SBFB)
}

重命名后移除 _CRS 方法中的以下内容(即上面代码注释的部分):

Interrupt (ResourceConsumer, Level, ActiveLow, Exclusive, ,, )
{
  0x0000006D,
}

寻找 GPIO Pin

在触控板的代码中寻找类似如下的代码片段:

Name (SBFG, ResourceTemplate ()
{
  	GpioInt (Level, ActiveLow, ExclusiveAndWake, PullDefault, 0x0000,
  		"\\_SB.PCI0.GPI0", 0x00, ResourceConsumer, ,
	)
  {   // Pin list
      0x0000
  }
})

如果找到的话,那么恭喜你,你的设备可能都不需要计算 GPIO Pin 值。没有找到也没有关系,我们可以手动添加,具体参考下面内容。

添加 GPIO 模板

很明显我的触控板搜索不到 GPIO 相关的代码,所以我们需要复制上面的代码片段,将其添加到 _CRS 方法下面:

Method (_CRS, 0, Serialized)  // _CRS: Current Resource Settings
{
  Name (SBFB, ResourceTemplate ()
        {
          I2cSerialBusV2 (0x0015, ControllerInitiated, 0x00061A80,
                          AddressingMode7Bit, "\\_SB.PCI0.I2C1",
                          0x00, ResourceConsumer, , Exclusive,
                         )
        })
    // 下面是新增的 GPIO 模板代码片段
    Name (SBFG, ResourceTemplate ()
          {
            GpioInt (Level, ActiveLow, ExclusiveAndWake, PullDefault, 0x0000,
                     "\\_SB.PCI0.GPI0", 0x00, ResourceConsumer, ,
                    )
            {   // Pin list
              0x0000   // 这个值 我们待会要计算一下
            }
          })
    Return (SBFB)
}

修改 _CRS 返回值

因为我们 _CRS 方法里面还引入了 SBFG 变量,所以得将默认的 Return (SBFB) 返回值修改为:

Return (ConcatenateResTemplate (SBFB, SBFG))

Bat.bat 大佬原话:有 gpioint 的设备不用算 pin,没有的加上也基本上不可能成功,算 pin 其实是最后死马当活马医的方案。

计算 GPIO Pin 值

我们上面添加的模板中,GPIO Pin 值为 0x0000,这个一般是跑不起来的,只能起占位的作用,所以需要计算一个正确的 GPIO 值。这一步比较关键,不同的 CPU 有不同的计算公式,下面的这个公式也是 Bat.bat 大佬提供的:

  • Skylake(intel 6 代 CPU)
If APICPIN > 47 And APICPIN <= 79 Then     
    GPIOPIN = APICPIN - 24   
    GPIOPIN2 = APICPIN + 72  
ElseIf APICPIN > 79 And APICPIN <= 119 Then
    GPIOPIN = APICPIN - 24
End If
  • CoffeeLake-H(intel 8 代标压 CPU)
If APICPIN > 47 And APICPIN <= 71 Then   
    GPIOPIN = APICPIN - 16   
    GPIOPIN2 = APICPIN + 240 
    If APICPIN > 47 And APICPIN <= 59 Then GPIOPIN3 = APICPIN + 304  
ElseIf APICPIN > 71 And APICPIN <= 95 Then 
    GPIOPIN = APICPIN - 8    
    GPIOPIN3 = APICPIN + 152
    GPIOPIN2 = APICPIN + 120 
ElseIf APICPIN > 95 And APICPIN <= 119 Then 
    GPIOPIN = APICPIN        
If APICPIN > 108 And APICPIN <= 115 Then 
  	GPIOPIN2 = APICPIN + 20 
End If
  • CoffeeLake-LF 和 Whiskylake(intel 8 代低压 CPU 和 Whiskylake 架构 CPU)
If APICPIN > 47 And APICPIN <= 71 Then      
    GPIOPIN = APICPIN - 16   
    GPIOPIN2 = APICPIN + 80  
ElseIf APICPIN > 71 And APICPIN <= 95 Then  
    GPIOPIN2 = APICPIN + 184 
    GPIOPIN = APICPIN + 88   
ElseIf APICPIN > 95 And APICPIN <= 119 Then 
    GPIOPIN = APICPIN        
ElseIf APICPIN > 108 And APICPIN <= 115 Then 
  	GPIOPIN2 = APICPIN - 44 
End If

我的笔记本 CPU 是 i7-10510U,属于 Comet Lake 的低压 CPU,会发现上述公式并没有 Comet Lake 系列的,不过 Bat.bat 大佬说 10 代就是 8 代的马甲,所以使用 CoffeeLake-LF 和 Whiskylake 公式即可,带入公式可计算出我们的 GPIO Pin 的十进制:

我们的 APICPIN 的 16进制为 6d,转换为 10 进制为 109,满足公式如下条件:

ElseIf APICPIN > 95 And APICPIN <= 119 Then 
    GPIOPIN = APICPIN    // GPIOPIN = 109

即 GPIOPIN = 109,转换为 16 进制就是 6d

同时会发现我们的 APICPIN 也满足如下条件:

ElseIf APICPIN > 108 And APICPIN <= 115 Then 
  	GPIOPIN2 = APICPIN - 44  // GPIOPIN2 = 109 - 44 = 65

即GPIOPIN = 65,转换为 16 进制就是 41

可以看到我们算出了 2 个值,分别是 6d41,得一个个尝试去验证就可以了。

在极少数情况下,计算出来的 GPIO Pin 值不起作用。在这种情况下,你可以尝试一些常见的值:0x170x1B0x340x55

最终国光尝试了我的这个 GDX1515 触控板的 GPIO Pin 值为 6d

放一个贴图,这样大家看起来应该会比较直观一点。

最终效果

使用IORegistryExplorer 可以看到我们的这个 GDX1515 冷门小众的触控板终于工作在 GPIO 中断模式下了,可以看到出现了 gpioPingpioIRQ 属性值,完美:

再来看下 dmesg 的日志情况:

# 发现了 GDX1515 的 I2C 协议的触控板
[   44.313108]: VoodooI2CControllerDriver::pci8086,2e9 Found I2C device: GDX1515

# ETPD 发现了有效的 _CRS 方法
[   44.313178]: VoodooI2CDeviceNub::ETPD Found valid resources from _CRS method
[   44.313182]: VoodooI2CControllerDriver::pci8086,2e8 Got bus configuration values
[   44.313231]: VoodooI2CDeviceNub::ETPD Returned index 0x0 from _DSM or XDSM method is not supported
[   44.313235]: VoodooI2CDeviceNub::ETPD Could not retrieve resources from _DSM or XDSM method

# ETPD 发现了有效的 GPIO 中断
[   44.313244]: VoodooI2CDeviceNub::ETPD Found valid GPIO interrupts
[   44.313344]: VoodooI2CControllerDriver::pci8086,2e8 Publishing device nubs

# ETPD 得到 GPIO 控制器
[   44.313347]: VoodooI2CDeviceNub::ETPD Got GPIO Controller! VoodooGPIOCannonLakeLP
[   44.816012]: VoodooI2CHIDDevice:0x100000738 start

# GDX1515 设备启动的重置完成
[   44.919729]: VoodooI2CHIDDevice::GDX1515 Device initiated reset accomplished
[   45.050029]: VoodooI2CHIDDevice:0x100000738 creating interfaces
[   45.051068]: VoodooI2CHIDDevice:0x100000738 Matching has vendor DeviceUsagePage : ff0c bundleIdentifier com.apple.AppleUserHIDDrivers ioclass AppleUserHIDEventService but transport and vendorID is missing
[   45.053582]: VoodooI2CPrecisionTouchpadHIDEventDriver:0x10000073d start
[   45.059693]: open by VoodooI2CPrecisionTouchpadHIDEventDriver 0x10000073d (0x0)

# GDX1515 进入  Precision Touchpad Mode (PTP)模式,即高精度触控板模式
[   45.059739]: VoodooI2CPrecisionTouchpadHIDEventDriver::GDX1515 Putting device into Precision Touchpad Mode

从日志中也可以看到完美工作了,工作在 GPIO 中断模式下。

为了有对比,下面再附上没有工作在 GPIO 中断模式下的日志情况:

  • 没有定制 DSDT 在轮询下的日志
[   48.517816]: VoodooI2CControllerDriver::pci8086,2e9 Found I2C device: GDX1515
[   48.517869]: VoodooI2CDeviceNub::ETPD Found valid resources from _CRS method
[   48.517913]: VoodooI2CDeviceNub::ETPD Returned index 0x0 from _DSM or XDSM method is not supported
[   48.517917]: VoodooI2CDeviceNub::ETPD Could not retrieve resources from _DSM or XDSM method
# ETPD 警告,发现了不兼容的 APIC pin 值 6d,它是大于 2f 的
[   48.517925]: VoodooI2CDeviceNub::ETPD Warning: Incompatible APIC interrupt pin (0x6d > 0x2f)

# ETPD 找不到任何 APIC 或 GPIO 中断。 您可能将在轮询模式下运行。
[   48.517931]: VoodooI2CDeviceNub::ETPD Warning: Could not find any APIC nor GPIO interrupts. Your chosen satellite will run in polling mode if implemented.
[   48.519529]: VoodooI2CHIDDevice:0x100000725 start

# GDX1515 无法获取中断事件源,改用轮询模式
[   48.519539]: VoodooI2CHIDDevice::GDX1515 Warning: Could not get interrupt event source, using polling instead
[   48.722472]: VoodooI2CHIDDevice::GDX1515 Device initiated reset accomplished
[   48.854707]: VoodooI2CHIDDevice:0x100000725 creating interfaces
[   48.856351]: VoodooI2CHIDDevice:0x100000725 Matching has vendor DeviceUsagePage : ff0c bundleIdentifier com.apple.AppleUserHIDDrivers ioclass AppleUserHIDEventService but transport and vendorID is missing
[   48.860549]: VoodooI2CPrecisionTouchpadHIDEventDriver:0x10000072a start
[   48.866671]: open by VoodooI2CPrecisionTouchpadHIDEventDriver 0x10000072a (0x0)

# GDX1515 进入  Precision Touchpad Mode (PTP)模式,即高精度触控板模式
[   48.866716]: VoodooI2CPrecisionTouchpadHIDEventDriver::GDX1515 Putting device into Precision Touchpad Mode
  • 定制了 DSDT,但 GPIO 不正确的日志
[   37.835221]: VoodooI2CControllerDriver::pci8086,2e9 Found I2C device: GDX1515
[   37.835266]: VoodooI2CDeviceNub::ETPD Found valid resources from _CRS method
[   37.835298]: VoodooI2CDeviceNub::ETPD Returned index 0x0 from _DSM or XDSM method is not supported
[   37.835306]: VoodooI2CDeviceNub::ETPD Could not retrieve resources from _DSM or XDSM method
[   37.835309]: KextLog: AuxKC bundle com.alexandred.VoodooI2CServices marked as loadable

# ETPD 发现了有效的 GPIO 中断
[   37.835312]: VoodooI2CDeviceNub::ETPD Found valid GPIO interrupts
[   37.835408]: VoodooI2CDeviceNub::ETPD Got GPIO Controller! VoodooGPIOCannonLakeLP
[   38.337232]: VoodooI2CHIDDevice:0x100000761 start

# 无法获取 GPIO 引脚的硬件引脚 81(错误的 GPIO Pin 的10进制),无法获取中断事件源,改用轮询
[   38.337263]: VoodooGPIOCannonLakeLP::Failed getting hardware pin for GPIO pin 81VoodooI2CHIDDevice::GDX1515 Warning: Could not get interrupt event source, using polling instead
[   38.539715]: VoodooI2CHIDDevice::GDX1515 Device initiated reset accomplished
[   38.670485]: VoodooI2CHIDDevice:0x100000761 creating interfaces
[   38.672136]: VoodooI2CHIDDevice:0x100000761 Matching has vendor DeviceUsagePage : ff0c bundleIdentifier com.apple.AppleUserHIDDrivers ioclass AppleUserHIDEventService but transport and vendorID is missing
[   38.676469]: VoodooI2CPrecisionTouchpadHIDEventDriver:0x100000766 start
[   38.682612]: open by VoodooI2CPrecisionTouchpadHIDEventDriver 0x100000766 (0x0)

# GDX1515 进入  Precision Touchpad Mode (PTP)模式,即高精度触控板模式
[   38.682668]: VoodooI2CPrecisionTouchpadHIDEventDriver::GDX1515 Putting device into Precision Touchpad Mode

炫耀一下

双屏笔记本真的很帅,等我最近做一期视频记录一下,到时候也又可以去 B 站水了:

参考资料

鸣谢

感谢 Github 上面国外的路人网友 gvkt,我本来都要放弃这个触控板的,都是因为他的坚持探索,才给我了希望,黑苹果指路人!

本教程的触控板中断部分,也请教了 Bat.bat 大佬,大佬给予了许多帮助,给我科普了很多网上搜索不到的姿势,这让我对黑苹果下触控板的工作模式理解更加透彻一点了,雀氏牛皮!

支持一下

本文可能实际上也没有啥技术含量,但是写起来还是比较浪费时间的,在这个喧嚣浮躁的时代,个人博客越来越没有人看了,写博客感觉一直是用爱发电的状态。如果你恰巧财力雄厚,感觉本文对你有所帮助的话,可以考虑打赏一下本文,用以维持高昂的服务器运营费用(域名费用、服务器费用、CDN费用等)

微信
支付宝

没想到文章加入打赏列表没几天 就有热心网友打赏了 于是国光我用 Bootstrap 重写了一个页面用以感谢支持我的朋友,详情请看 打赏列表 | 国光


文章作者:  国光
版权声明:  本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 国光 !
 本篇
双屏笔记本在 macOS 下的触控板驱动研究 双屏笔记本在 macOS 下的触控板驱动研究
实际上关于黑苹果的文章本博客不打算再记录的了,之前还单独开了个子域名用于记录黑苹果:国光的黑苹果安装教程,但是呢由于这个触控板真的耗费了我太多精力了,最终搞定了还是比较有成就感的,所以就单独来博客写文章记录一下,而且也可能会帮助到其他看到本
2021-12-19
下一篇 
ByteCTF 2021 Final SEO WP ByteCTF 2021 Final SEO WP
这道题实际上在初赛前就出好了,但是没想到被选到了线下决赛了,正好前几天决赛打完了,所以就顺便开源了这道题目,顺便来水一篇文章,证明一下我这个博客还活着。
2021-12-17
  目录