给 MacBook 启用 Windows 连接待机

imbushuo

Please mind the gap

53人赞了该文章

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 不支持 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

由于平台设计的不同和特殊设备的不确定性,我对这个项目在其他笔记本设备上使用造成的后果概不负责。你应该只在低电压的处理器上尝试这个项目,且设备最好能不需要风扇运行较长时间。

发布于2018-07-08 18:10
编辑于2018-07-10 17:58

文章被以下专栏收录