在Linux上为AMD Raven Ridge (ThinkPad E485) 做的一些研究

农企翻身啦!Ryzen发布之后,作为AMD的铁杆粉,我购入了两台AMD设备。分别是R3 2200G + Asrock AB350M-HDV的台式机和R7 2700U的ThinkPad E485笔记本。都是Ryzen的Raven Ridge系列APU产品。选择Raven是因为一来我不需要特别高的Gaming性能,也就玩玩KSP,Overwatch这点需求,Raven的Vega GFX足以满足我的图形需求,而Zen的核保证了CPU的性能我也可以接受;二来,Raven Ridge的UMA VRAM结构从IC的角度上来讲实在是非常惊艳,我对他赞赏有加。

AMD天地良心,对于R3 2200G以及其他Ryzen Desktop产品,都不禁止超频。作为追求性价比的穷逼,自然是到手超到4.2Ghz(逃)。这台台式机在更新了BIOS之后配最新内核启动毫无障碍,可以说是非常适合Linux了。

ThinkPad E485则遇到了些问题,启动之后屏幕没有输出。强行指定使用efifb显示驱动之后发现有报[Firmware Bug],一番检索,发现是IOMMU的问题,喂给IOMMU的ACPI IVRS表里缺了一个中断的路由,导致IOMMU无法正确初始化,通过在cmdline里手动补全IVRS映射就可以了,具体可以参见一篇博文:ThinkPad E485/E585 - Firmware bug ACPI IVRS table

于是美滋滋的开始搭配Linux-4.19内核日用E485。一日,编译内核的时候闲心大作,用cpupower monitor看看频率,突然发现频率始终维持在2.2Ghz,也就是默频。然而按照AMD的参数,R7 2700U的最高频率应该有3.8Ghz,并且这台电脑在Windows下在高负载下也时常睿频到3.5Ghz。但是在Linux下睿频似乎完全没有启用,不能忍。

睿频 长篇大论

用cpupower frequency-info命令一看,Boost: Disabled,哈,睿频没开。

以Linux AMD Boost为关键词上网稍稍检索,就找到了 Kernel CPUFreq Boost 子系统的文档,明确提到Boost,也就是所谓睿频子系统的开关位于一个sysfs里:/sys/devices/system/cpu/cpufreq/boost。在电脑上一找,发现并没有这个开关。于是立刻怀疑是BIOS禁用了这个功能,然而BIOS下并没有这个功能的开关。百般无奈,只好读起了内核的代码。无数次追Arm和MIPS架构的代码来Debug,可没想到现在x86下也要干起这种事情。

对于追内核代码,这里不得不推荐Bootlin的Elixir Cross Referencer。追起函数来非常方便。

先从boost switch的驱动入手,所有比K8新的AMD CPU,都在使用 acpi-cpufreq.c 这个通用的基于ACPI表控制频率的驱动。cpufreq是个非常复杂的子系统,我就暂且跳到boost部分的初始化函数里去追了。

static void __init acpi_cpufreq_boost_init(void)
{
	int ret;

	if (!(boot_cpu_has(X86_FEATURE_CPB) || boot_cpu_has(X86_FEATURE_IDA)))
		return;

	acpi_cpufreq_driver.set_boost = set_boost;
	acpi_cpufreq_driver.boost_enabled = boost_state(0);

	/*
	 * This calls the online callback on all online cpu and forces all
	 * MSRs to the same value.
	 */
	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpufreq/acpi:online",
				cpufreq_boost_online, cpufreq_boost_down_prep);
	if (ret < 0) {
		pr_err("acpi_cpufreq: failed to register hotplug callbacks\n");
		return;
	}
	acpi_cpufreq_online = ret;
}

这段代码看上去并没有什么问题,根据CPU的Feature Bit中包不包含IDA或者CPB来决定要不要去注册sysfs node和相应的callback。(IDA指Intel的 Intel Dynamic Acceleration,CPB指AMD的Core Performance Boost,均为不同的睿频技术。)

继续百思不得其解,无奈之下加了个printk输出一下CPU Feature Bit的情况,发现X86_FEATURE_CPB真的没有被设置。

那么就去追X86_FEATURE_CPB上哪里设置的,追到 arch/x86/kernel/cpu/scattered.c的 init_scattered_cpuid_features函数,通过match x86特有的cpuid寄存器里的bit来设定各个Feature Cap,根据前面的Struct,CPUID_EDX 0x80000007的Bit9代表了有无CPB功能。难道说这里的判断代码有问题?理论上这种关键的代码是几乎不可能有问题的。

我直接dump出了CPUID_EDX 0x80000007的值,是0x00006599,Bit9的确是0,意味着不支持CPB。我一下子傻了眼,难道CPU真的不支持睿频,不可能。或者Ryzen改变了寄存器定义?找到了AMD公开的文档Open-Source Register Reference for AMD Family 17h Processors,CPU ID CPB Bit的确是CPUID_EDX 0x80000007 Bit9没错。这下我彻底傻眼了。

百般无奈,继续看起了内核的代码,突然注意到还有一处地方引用了X86_FEATURE_CPB,arch/x86/kernel/cpu/amd.c的 init_amd_zn 函数:

static void init_amd_zn(struct cpuinfo_x86 *c)
{
	set_cpu_cap(c, X86_FEATURE_ZEN);
	/*
	 * Fix erratum 1076: CPB feature bit not being set in CPUID. It affects
	 * all up to and including B1.
	 */
	if (c->x86_model <= 1 && c->x86_stepping <= 1)
		set_cpu_cap(c, X86_FEATURE_CPB);
}

啥?Erratum?勘误?且让我查查 Revision Guide for AMD Family 17h Processors。我去,cpuid里CPB bit没有设置真的是个勘误。但是这份勘误表里没有提到x86_model 17h也就是Raven Ridge的勘误,只提到了初代Ryzen,所以内核里也没有针对Raven Ridge做修复。但是Raven估计是沿用了来自Zen的核,所以继承了这个Bug。没想到怀疑到最后竟然是硬件问题,心累啊。

赶紧给内核交个补丁,CC Linux-Stable,修了这坑,省得残害更多人的头发。

然而。。事情并没有这么简单,睡觉去了,下回再更。。

悲伤的故事 休眠/唤醒

因为Watchdog在ACPI S3 Resume过程中Reset了CPU而导致唤醒失败。。。

Licensed under CC BY 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy