Skip to content

Commit 7dbdba0

Browse files
authored
initial Turin CPU platform (#9043)
this is so much simpler than codifying all the of the bits describing all of the CPU surface area! what a breath of fresh air! The feature selection here is the intersection of "PPR says it's there", "useful for guests", "supported in byhve/propolis", and "doesn't seem like we're painted into a corner if a future platform changes it." the bits here are, also, a subset of what what I'd seen on a 9365 in a Cosmo. While byhve/Propolis would let guests turn on AutoIBRS, I haven't looked at it in the context of guest OSes much at all (though they do _boot_ when told they're allowed to use AutoIBRS). UAI is in a similar boat but I don't think anyone uses it. So both EFER features are hidden for the time being. Otherwise, as-is, I've booted Linux, Windows, OmniOS, and FreeBSD with this profile and they seem fine. Linux for example omits mentioning caches in `/proc/cpuinfo`, which make sense since I've avoided as much cache topology information as I can here.. the reasoning for _that_ is discussed more in RFD 314.
1 parent d743754 commit 7dbdba0

File tree

4 files changed

+331
-7
lines changed

4 files changed

+331
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ rand_distr = "0.5.1"
660660
rand_seeder = "0.4.0"
661661
range-requests = { path = "range-requests" }
662662
ratatui = "0.29.0"
663-
raw-cpuid = { git = "https://github.com/oxidecomputer/rust-cpuid.git", rev = "0a8dbd2311263f6a59ea58089e33c8331436ff3a" }
663+
raw-cpuid = { git = "https://github.com/oxidecomputer/rust-cpuid.git", rev = "a4cf01df76f35430ff5d39dc2fe470bcb953503b" }
664664
rayon = "1.10"
665665
rcgen = "0.12.1"
666666
reconfigurator-cli = { path = "dev-tools/reconfigurator-cli" }

nexus/src/app/instance_platform/cpu_platform.rs

Lines changed: 322 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ pub fn functionally_same(base: CpuIdDump, target: CpuIdDump) -> bool {
104104
if base_info.has_fp256() != target_info.has_fp256() {
105105
return false;
106106
}
107+
108+
// TODO: same as above: we probably just need to require "base" has
109+
// the same or wider FPU datapath than "target"
110+
if base_info.has_fp512() != target_info.has_fp512() {
111+
return false;
112+
}
107113
}
108114
_ => {
109115
// Specific cases here may be acceptable, but for expediency (and
@@ -500,8 +506,8 @@ fn milan_ideal() -> CpuIdDump {
500506

501507
// Set up processor optimization info (leaf 8000_001Ah)
502508
let mut leaf = PerformanceOptimizationInfo::empty();
503-
leaf.set_movu(true); // TODO: BREAKING
504-
leaf.set_fp256(true); // TODO: BREAKINGISH?
509+
leaf.set_movu(true);
510+
leaf.set_fp256(true);
505511
cpuid
506512
.set_performance_optimization_info(Some(leaf))
507513
.expect("can set leaf 8000_001Ah");
@@ -548,6 +554,249 @@ fn milan_ideal() -> CpuIdDump {
548554
dump
549555
}
550556

557+
pub fn turin_v1() -> CpuIdDump {
558+
// For VMs, a Turin-like CPU is very much like Milan with AVX-512 features,
559+
// so start from Milan.
560+
let baseline = milan_ideal();
561+
562+
let mut cpuid = CpuId::with_cpuid_reader(baseline);
563+
564+
let mut leaf =
565+
cpuid.get_feature_info().expect("baseline Milan defines leaf 1");
566+
567+
// Set up EAX: Family 1Ah model 2h stepping 1.
568+
//
569+
// This corresponds to processor revision C1, the production stepping of Turin processors.
570+
leaf.set_extended_family_id(0x0B);
571+
leaf.set_base_family_id(0x0F);
572+
leaf.set_base_model_id(0x02);
573+
leaf.set_stepping_id(0x01);
574+
575+
// EBX, ECX, EDX are all unchanged from Milan (same cache line flush size,
576+
// leaf 1 features are unchanged)
577+
578+
cpuid.set_feature_info(Some(leaf)).expect("can set leaf 1");
579+
580+
let mut leaf = cpuid
581+
.get_extended_feature_info()
582+
.expect("baseline Milan defines leaf 7");
583+
584+
// Same as with initial Milan profiles, `rdseed` is not supported by the
585+
// virt stack, so we should hide it from guests for now.
586+
leaf.set_rdseed(false);
587+
588+
// Turin supports the TSC_ADJUST MSR but guest plumbing is not present for
589+
// it and it's not clear what a guest would productively do with it anyway.
590+
leaf.set_tsc_adjust_msr(false);
591+
592+
// Turin supports MOVDIR64B and MOVDIRI, so pass them through.
593+
leaf.set_movdir64b(true);
594+
leaf.set_movdiri(true);
595+
596+
// These AVX512 features are present for all Turin processors.
597+
leaf.set_avx512f(true);
598+
leaf.set_avx512dq(true);
599+
leaf.set_avx512_ifma(true);
600+
leaf.set_avx512cd(true);
601+
leaf.set_avx512bw(true);
602+
leaf.set_avx512vl(true);
603+
604+
leaf.set_avx512vbmi(true);
605+
leaf.set_avx512vbmi2(true);
606+
leaf.set_gfni(true);
607+
leaf.set_avx512vnni(true);
608+
leaf.set_avx512bitalg(true);
609+
leaf.set_avx512vpopcntdq(true);
610+
// While hardware supports 57-bit virtual addresses, the bhyve support is
611+
// not there yet.
612+
leaf.set_la57(false);
613+
614+
leaf.set_avx512_vp2intersect(true);
615+
616+
leaf.set_avx512_bf16(true);
617+
leaf.set_avx_vnni(true);
618+
619+
cpuid.set_extended_feature_info(Some(leaf)).expect("can set leaf 7h");
620+
621+
// This is the same information for leaf D as in Milan, but with the new
622+
// AVX-512 bits in Turin.
623+
// TODO: kind of gross to have to pass an empty `CpuIdDump` here...
624+
let mut state = ExtendedStateInfo::empty(CpuIdDump::new());
625+
state.set_xcr0_supports_legacy_x87(true);
626+
state.set_xcr0_supports_sse_128(true);
627+
state.set_xcr0_supports_avx_256(true);
628+
// Update leaf D for the larger XCR0 set
629+
state.set_xcr0_supports_avx512_opmask(true);
630+
state.set_xcr0_supports_avx512_zmm_hi256(true);
631+
state.set_xcr0_supports_avx512_zmm_hi16(true);
632+
// Managed dynamically in practice.
633+
state.set_xsave_area_size_enabled_features(0x980);
634+
// `Core::X86::Cpuid::ProcExtStateEnumEcx00`, but minus the MPK support we
635+
// don't make available to guests.
636+
state.set_xsave_area_size_supported_features(0x980);
637+
638+
state.set_xsaveopt(true);
639+
state.set_xsavec(true);
640+
state.set_xgetbv(true);
641+
state.set_xsave_size(0x980);
642+
643+
let mut leaves = state.into_leaves().to_vec();
644+
let mut ymm_state = ExtendedState::empty();
645+
ymm_state.set_size(0x100);
646+
ymm_state.set_offset(0x240);
647+
leaves.push(Some(ymm_state.into_leaf()));
648+
// level 3
649+
leaves.push(None);
650+
// level 4
651+
leaves.push(None);
652+
// levels 5, 6, and 7 are described in the PPR:
653+
// `Core::X86::Cpuid::ProcExtStateEnumEax06`
654+
//
655+
// level 5
656+
let mut kregs_state = ExtendedState::empty();
657+
kregs_state.set_size(0x040);
658+
kregs_state.set_offset(0x340);
659+
leaves.push(Some(kregs_state.into_leaf()));
660+
// level 6
661+
let mut zmmhi_state = ExtendedState::empty();
662+
zmmhi_state.set_size(0x200);
663+
zmmhi_state.set_offset(0x380);
664+
leaves.push(Some(zmmhi_state.into_leaf()));
665+
// level 7
666+
let mut zmmhi16_state = ExtendedState::empty();
667+
zmmhi16_state.set_size(0x400);
668+
zmmhi16_state.set_offset(0x580);
669+
leaves.push(Some(zmmhi16_state.into_leaf()));
670+
671+
cpuid.set_extended_state_info(Some(&leaves[..])).expect("can set leaf Dh");
672+
673+
let mut leaf = cpuid
674+
.get_extended_processor_and_feature_identifiers()
675+
.expect("baseline Milan defines leaf 8000_0001");
676+
677+
// This is the same as the leaf 1 EAX configured earlier.
678+
leaf.set_extended_signature(0x00B00F21);
679+
680+
// Hide topology extensions. We'd want to set this and set
681+
// ThreadsPerComputeUnit to indicate SMT is active, but we'd run afoul of
682+
// https://github.com/oxidecomputer/propolis/issues/940, which in turn
683+
// really needs us to disallow VM shapes with odd vCPU counts. For now, just
684+
// hide topology extensions and we'll get sockets into shape in a later CPU
685+
// platform rev.
686+
leaf.set_topology_extensions(false);
687+
// This is just strange. bhyve supports all six performance counters, so we
688+
// *should* be free to set this bit. Linux is fine with this. But
689+
// experimentally I've seen that with this bit set and TopologyExtensions
690+
// *not* set (and leaves 8000_001D,8000_001E zeroed), Windows Server 2022
691+
// gets into an infinite loop somewhere early in boot.
692+
//
693+
// We want to hide topology extensions for a bit still - we'd like to
694+
// indicate SMT there, but that wants some other changes (see above or
695+
// Propolis#940)
696+
//
697+
// So, if we don't have TopologyExtensions, apparently Windows can't have
698+
// six perf counters?
699+
leaf.set_perf_cntr_extensions(false);
700+
// RDTSCP requires some bhyve and Propolis work to support, so it is masked
701+
// off for now.
702+
leaf.set_rdtscp(false);
703+
cpuid
704+
.set_extended_processor_and_feature_identifiers(Some(leaf))
705+
.expect("can set leaf 8000_0001h");
706+
707+
cpuid
708+
.set_processor_brand_string(Some(b"Oxide Virtual Turin-like Processor"))
709+
.expect("can set vCPU brand string");
710+
711+
let mut leaf = cpuid
712+
.get_processor_capacity_feature_info()
713+
.expect("can get leaf 8000_0008h");
714+
715+
// Support for `wbnoinvd` is hidden in bhyve for the time being. This would
716+
// probably be fine to pass through, but it is as-yet untested. Continue
717+
// hiding this instruction.
718+
leaf.set_wbnoinvd(false);
719+
720+
// "Processor is not vulnerable to Branch Type Confusion"
721+
// This is 1 for all Turin processors and does not require particular MSR
722+
// settings or hypervisor support, so pass it along.
723+
leaf.set_btc_no(true);
724+
725+
// BSFD, SSBD, STIBP, and IBRS, are all supported on Turin, but guests
726+
// are not yet allowed to access SPEC_CTRL to enable (or confirm they are
727+
// enabled).
728+
leaf.set_psfd(false);
729+
leaf.set_ssbd(false);
730+
leaf.set_stibp(false);
731+
leaf.set_ibrs(false);
732+
733+
cpuid
734+
.set_processor_capacity_feature_info(Some(leaf))
735+
.expect("can set leaf 8000_0008h");
736+
737+
let mut leaf = cpuid
738+
.get_performance_optimization_info()
739+
.expect("baseline Milan defines 8000_001Ah");
740+
leaf.set_fp256(false);
741+
leaf.set_fp512(true);
742+
cpuid
743+
.set_performance_optimization_info(Some(leaf))
744+
.expect("can set leaf 8000_001Ah");
745+
746+
let mut leaf = cpuid
747+
.get_extended_feature_identification_2()
748+
.expect("can get leaf 8000_0021h");
749+
750+
// We don't support access to MSR `BP_CFG`, so SRSO_MSR_FIX stays hidden.
751+
leaf.set_srso_msr_fix(false);
752+
// SRSO_USER_KERNEL_NO is advice about vulnerabilities the processor is not
753+
// affected by; no bhyve/Propolis support needed.
754+
leaf.set_srso_user_kernel_no(true);
755+
// SRSO_NO, more generally, is clear on Turin.
756+
leaf.set_srso_no(false);
757+
// IBPB_BRTYPE and SBPB are hidden because PRED_CMD and SPEC_CTRL generally
758+
// aren't guest-accessible yet.
759+
leaf.set_ibpb_brtype(false);
760+
leaf.set_sbpb(false);
761+
// Enhanced return address predictor security is another "this is just how
762+
// the processor behaves" bit.
763+
leaf.set_eraps(true);
764+
leaf.set_prefetchi(true);
765+
// FP512 downgrade is configurable via MSR, but the MSR is not made
766+
// available to guests. The other bits are present on all Turin processors.
767+
leaf.set_fp512_downgrade(false);
768+
leaf.set_fast_rep_scasb(true);
769+
leaf.set_epsf(true);
770+
leaf.set_opcode_0f_017_reclaim(true);
771+
leaf.set_amd_ermsb(true);
772+
leaf.set_fast_short_repe_cmpsb(true);
773+
leaf.set_fast_short_rep_stosb(true);
774+
// The EFER write is permitted in bhyve, so this *should* work? But I'm not
775+
// very familiar with ohw this is used in practice or where guest OSes would
776+
// find it beneficial. Hide it for now and we'll come back to this for a
777+
// broader speculative controls enablement with SPEC_CTRL/PRED_CMD later.
778+
leaf.set_automatic_ibrs(false);
779+
// The EFER write is permitted in bhyve, so this *should* work? But the
780+
// forward utility of this bit is not as clear, so hide it.
781+
leaf.set_upper_address_ignore(false);
782+
// Architectural behavior, so we should pass this through.
783+
leaf.set_fs_gs_base_write_not_serializing(true);
784+
785+
cpuid
786+
.set_extended_feature_identification_2(Some(leaf))
787+
.expect("can set leaf 8000_0021h");
788+
789+
// Cache topology leaves are otherwise left zeroed; if we can avoid getting
790+
// into it, let's try!
791+
792+
let mut source = cpuid.into_source();
793+
// We've cleared `topology_extensions` above, now remove the leaves so
794+
// Propolis doesn't try specializing these; we don't want them presented yet!
795+
source.set_leaf(0x8000_001D, None);
796+
source.set_leaf(0x8000_001E, None);
797+
source
798+
}
799+
551800
pub fn milan_rfd314() -> CpuIdDump {
552801
// This is the Milan we'd "want" to expose, absent any other constraints.
553802
let baseline = milan_ideal();
@@ -753,7 +1002,7 @@ pub fn dump_to_cpuid_entries(dump: CpuIdDump) -> Vec<CpuidEntry> {
7531002
#[cfg(test)]
7541003
mod test {
7551004
use crate::app::instance_platform::cpu_platform::{
756-
dump_to_cpuid_entries, milan_rfd314,
1005+
dump_to_cpuid_entries, milan_rfd314, turin_v1,
7571006
};
7581007
use raw_cpuid::{
7591008
CpuId, CpuIdReader, CpuIdResult, CpuIdWriter, L1CacheTlbInfo,
@@ -846,6 +1095,76 @@ mod test {
8461095
cpuid_leaf!(0x80000021, 0x00000045, 0x00000000, 0x00000000, 0x00000000),
8471096
];
8481097

1098+
// This CPUID leaf blob is some small tweaks on top of the "ideal Milan",
1099+
// maintaining some details that are disabled due to needed bhyve support
1100+
// and including Turin-specific features as supported and relevant to
1101+
// guests.
1102+
const TURIN_V1_CPUID: [CpuidEntry; 25] = [
1103+
cpuid_leaf!(0x0, 0x0000000D, 0x68747541, 0x444D4163, 0x69746E65),
1104+
cpuid_leaf!(0x1, 0x00B00F21, 0x00000800, 0xF6D83203, 0x078BFBFF),
1105+
cpuid_leaf!(0x5, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
1106+
cpuid_leaf!(0x6, 0x00000004, 0x00000000, 0x00000000, 0x00000000),
1107+
cpuid_subleaf!(
1108+
0x7, 0x0, 0x00000001, 0xF1BB03A9, 0x18005F42, 0x00000110
1109+
),
1110+
cpuid_subleaf!(
1111+
0x7, 0x1, 0x00000030, 0x00000000, 0x00000000, 0x00000000
1112+
),
1113+
cpuid_subleaf!(
1114+
0xD, 0x0, 0x000000E7, 0x00000980, 0x00000980, 0x00000000
1115+
),
1116+
cpuid_subleaf!(
1117+
0xD, 0x1, 0x00000007, 0x00000980, 0x00000000, 0x00000000
1118+
),
1119+
cpuid_subleaf!(
1120+
0xD, 0x2, 0x00000100, 0x00000240, 0x00000000, 0x00000000
1121+
),
1122+
/*
1123+
* subleaves 3 and 4 are all-zero
1124+
*/
1125+
cpuid_subleaf!(
1126+
0xD, 0x5, 0x00000040, 0x00000340, 0x00000000, 0x00000000
1127+
),
1128+
cpuid_subleaf!(
1129+
0xD, 0x6, 0x00000200, 0x00000380, 0x00000000, 0x00000000
1130+
),
1131+
cpuid_subleaf!(
1132+
0xD, 0x7, 0x00000400, 0x00000580, 0x00000000, 0x00000000
1133+
),
1134+
cpuid_leaf!(0x80000000, 0x80000021, 0x68747541, 0x444D4163, 0x69746E65),
1135+
cpuid_leaf!(0x80000001, 0x00B00F21, 0x40000000, 0x440001F1, 0x25D3FBFF),
1136+
cpuid_leaf!(0x80000002, 0x6469784F, 0x69562065, 0x61757472, 0x7554206C),
1137+
cpuid_leaf!(0x80000003, 0x2D6E6972, 0x656B696C, 0x6F725020, 0x73736563),
1138+
cpuid_leaf!(0x80000004, 0x2020726F, 0x20202020, 0x20202020, 0x00202020),
1139+
cpuid_leaf!(0x80000007, 0x00000000, 0x00000000, 0x00000000, 0x00000100),
1140+
cpuid_leaf!(0x80000008, 0x00003030, 0x20000005, 0x00000000, 0x00000000),
1141+
cpuid_leaf!(0x8000000A, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
1142+
cpuid_leaf!(0x8000001A, 0x0000000A, 0x00000000, 0x00000000, 0x00000000),
1143+
cpuid_leaf!(0x8000001B, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
1144+
cpuid_leaf!(0x8000001C, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
1145+
cpuid_leaf!(0x8000001F, 0x00000000, 0x00000000, 0x00000000, 0x00000000),
1146+
cpuid_leaf!(0x80000021, 0x411D8C47, 0x00000000, 0x00000000, 0x00000000),
1147+
];
1148+
1149+
// Test that Turin V1 matches the predetermined CPUID leaves written above
1150+
// (e.g. that the collection of builders behind `turin_v1` produce this
1151+
// profile as used for testing and elsewhere).
1152+
//
1153+
// This is largely "baseline Milan" with Turin-specific additions.
1154+
#[test]
1155+
fn turin_v1_is_as_described() {
1156+
let computed = dump_to_cpuid_entries(turin_v1());
1157+
1158+
for (l, r) in TURIN_V1_CPUID.iter().zip(computed.as_slice().iter()) {
1159+
eprintln!("comparing {:#08x}.{:?}", l.leaf, l.subleaf);
1160+
assert_eq!(
1161+
l, r,
1162+
"leaf 0x{:08x} (subleaf? {:?}) did not match",
1163+
l.leaf, l.subleaf
1164+
);
1165+
}
1166+
}
1167+
8491168
// Test that the initial RFD 314 definition matches what we compute as the
8501169
// CPUID profile with that configuration in `milan_rfd314()`.
8511170
#[test]

nexus/src/app/instance_platform/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,13 +509,18 @@ fn cpuid_from_vmm_cpu_platform(
509509
) -> Option<Cpuid> {
510510
let cpuid = match platform {
511511
db::model::VmmCpuPlatform::SledDefault => return None,
512-
db::model::VmmCpuPlatform::AmdMilan
513-
| db::model::VmmCpuPlatform::AmdTurin => Cpuid {
512+
db::model::VmmCpuPlatform::AmdMilan => Cpuid {
514513
entries: cpu_platform::dump_to_cpuid_entries(
515514
cpu_platform::milan_rfd314(),
516515
),
517516
vendor: CpuidVendor::Amd,
518517
},
518+
db::model::VmmCpuPlatform::AmdTurin => Cpuid {
519+
entries: cpu_platform::dump_to_cpuid_entries(
520+
cpu_platform::turin_v1(),
521+
),
522+
vendor: CpuidVendor::Amd,
523+
},
519524
};
520525

521526
Some(cpuid)

0 commit comments

Comments
 (0)