给 MacBook 启用 Windows 连接待机
imbushuo
Please mind the gap
MacBook 12-inch 使用的 CPU / SoC 整一个都是为 Windows 设计的,然后 Apple 强行拿来往 macOS 上用。于是装了 Windows 之后我在想怎么样可以让它更好用一点。因为之前写了好几个面向不同平台的 UEFI & ACPI 实现,我就想干脆启动 Connected Standby 好了。
下午三点钟在和 @James Swineson 讨论这事的可行性,然后晚上就写出来了。
Note: AMD 很可能没有 Connected Standby。
Note2: 本文也发了 V2EX。
更新
我们给 @James Swineson 的 MacBookPro11,1 也打开了 Connected Standby,而且表现比 New MacBook 更好。
在 MacBook 12-inch 上的效果
你可以看视频: https://twitter.com/imbushuo/status/1016015791827836929
- 你可以用触摸板 /键盘 /盖子的任意一种来唤醒设备
- Windows 唤醒速度比 macOS 还快好几倍(真的,笑死我了……)
- Windows 睡眠时可以一直保持网络连接,可以在睡眠时收邮件 /收推送 /IRC 挂机 /BT 下载(某些客户端支持)/Windows Update (比 Power Nap 更进了一步)
- Windows 睡眠时可以播放歌曲,只要是 UWP (本地歌曲或者网上的歌曲都可以)
- Windows 可以在睡眠时对系统进行一些维护和优化操作
- 差不多你得到了可能比 Surface 更好的体验
已知问题
Windows 不支持 S0ix (Connected Standby) 和 S3 的热切换,所以需要重装系统。
You cannot switch between S3 and Modern Standby by changing a setting in the BIOS. Switching the power model is not supported in Windows without a complete OS re-install.单独安装未知设备的驱动,而不是安装 Boot Camp 一个大包。Apple 的一两个服务和驱动会干扰 S0ix 的正常运行,从而导致你开机无限转圈。
已经做成了一个 UEFI 驱动(实际上 UEFI 驱动的入口点参数和应用程序一致),可以由 rEFInd 自动加载,且不影响 macOS 的睡眠功能(因为 macOS 不读这个 Flag)。
实现
不同于 ACPI S3,S0ix 并不需要 ACPI DSDT 引入特殊的 ACPI 方法。自 ACPI 5.0 后,只需要在 FADT 表里标记 S0 Low Power Idle: EFI_ACPI_5_0_LOW_POWER_S0_IDLE_CAPABLE 即可。如果支持 5.0+,EDK2 的宏的 5_0 换成对应版本,不过总之都是 Flag 的 Bit 21。标记好后重新算一遍 FADT 表的 Checksum。核心实现如下:
if (Rsdp != NULL && XsdtHeader != NULL)
{
UINT64* EntryAddress = (UINT64*)&XsdtHeader[1];
UINT32 EntryArraySize = (XsdtHeader->Length - sizeof(*XsdtHeader)) / sizeof(UINT64);
Print(L"XSDT: Count = %d\n", EntryArraySize);
for (UINT32 j = 0; j < EntryArraySize; j++)
{
EFI\_ACPI\_DESCRIPTION\_HEADER* Entry = (EFI\_ACPI\_DESCRIPTION\_HEADER*)((UINTN)EntryAddress[j]);
if (Entry->Signature != EFI\_ACPI\_2\_0\_FIXED\_ACPI\_DESCRIPTION\_TABLE\_SIGNATURE)
{
Print(L"%d: Not FADT table \n", j);
continue;
}
if (Entry->Revision < EFI\_ACPI\_5\_0\_FIXED\_ACPI\_DESCRIPTION\_TABLE\_REVISION)
{
Print(L"%d: FADT revision is below ACPI 5.0 \n", j);
continue;
}
Print(L"FADT table located. \n");
// Iteration completed
Fadt = (EFI\_ACPI\_5\_0\_FIXED\_ACPI\_DESCRIPTION\_TABLE*) Entry;
break;
}
}
if (Fadt != NULL)
{
Print(L"FADT Flags: 0x%x \n", Fadt->Flags);
if ((Fadt->Flags >> 21) & 1U)
{
Print(L"S0 Low Power Idle State Flag is already enabled on this platform \n");
}
else
{
Print(L"Setting S0 Low Power Idle State Flag \n");
// Low Power S0 Idle (V5) is bit 21, enable it
Fadt->Flags |= 1UL << 21;
// Re-calc checksum
Print(L"Setting new checksum \n");
SetAcpiSdtChecksum(Fadt);
Print(L"FADT patch completed. \n");
}
}
然后启动系统安装,启动系统初始化即可。目前这段代码需要每次开机跑一次,我也没做 Chainload,比较麻烦。我暂时用 rEFInd 解决。未来会加入 Chainload 来启动 Windows Boot Manager。
代码
见我的 Github Repo。
Disclaimer
由于平台设计的不同和特殊设备的不确定性,我对这个项目在其他笔记本设备上使用造成的后果概不负责。你应该只在低电压的处理器上尝试这个项目,且设备最好能不需要风扇运行较长时间。