1use anyhow::bail;
7use chipset_device::io::IoError;
8use chipset_device::io::IoResult;
9use chipset_device::mmio::RegisterMmioIntercept;
10use cxl_spec::CxlComponentRegisters;
11use cxl_spec::CxlFlexBusPortDvsecExtendedCapability;
12use cxl_spec::CxlPortDvsecExtendedCapability;
13use cxl_spec::CxlRegisterLocatorDvsecExtendedCapability;
14use cxl_spec::pci_registers::spec::flex_bus_port_dvsec::CxlFlexBusPortDvsecCapability;
15use cxl_spec::pci_registers::spec::register_locator_dvsec::CxlRegisterLocatorRegisterBir;
16use cxl_spec::pci_registers::spec::register_locator_dvsec::CxlRegisterLocatorRegisterBlockIdentifier;
17use cxl_spec::spec::CXL_COMPONENT_REGISTERS_SIZE_BYTES;
18use inspect::Inspect;
19use pci_bus::GenericPciBusDevice;
20use pci_core::bus_range::AssignedBusRange;
21use pci_core::capabilities::extended::PciExtendedCapability;
22use pci_core::capabilities::extended::acs::AcsExtendedCapability;
23use pci_core::capabilities::msi_cap::MsiCapability;
24use pci_core::capabilities::pci_express::PciExpressCapability;
25use pci_core::cfg_space_emu::BarMemoryKind;
26use pci_core::cfg_space_emu::ConfigSpaceType1Emulator;
27use pci_core::cfg_space_emu::DeviceBars;
28use pci_core::msi::MsiTarget;
29use pci_core::spec::caps::pci_express::DevicePortType;
30use pci_core::spec::hwid::HardwareIds;
31use std::sync::Arc;
32use vmcore::save_restore::RestoreError;
33use vmcore::save_restore::SaveError;
34use vmcore::save_restore::SaveRestore;
35
36const ACS_CAPABILITY_IMPLEMENTED_MASK: u16 = 0x00df;
37const ACS_CAPABILITY_ALLOWED_ROOT_OR_DSP_MASK: u16 = 0x00ff;
38const ACS_CAPABILITY_ALLOWED_USP_MASK: u16 = 0x0000;
39
40type CxlComponentRegistersSavedState = <CxlComponentRegisters as SaveRestore>::SavedState;
41const CXL_COMPONENT_ALLOWED_ACCESS_SIZES: [usize; 2] = [4, 8];
42
43fn validate_cxl_component_register_access(offset: u64, len: usize) -> Result<(), IoError> {
44 if !CXL_COMPONENT_ALLOWED_ACCESS_SIZES.contains(&len) {
45 return Err(IoError::InvalidAccessSize);
46 }
47
48 if !offset.is_multiple_of(len as u64) {
49 return Err(IoError::UnalignedAccess);
50 }
51
52 Ok(())
53}
54
55#[derive(Debug, Default, Clone)]
60pub struct PciePortSettings {
61 pub acs_capabilities_supported: u16,
64
65 pub cxl_flex_bus_port_capability: Option<CxlFlexBusPortDvsecCapability>,
70}
71
72#[derive(Clone)]
74pub struct PortBarDefinition {
75 pub index: u8,
77 pub size_bytes: u64,
79 pub subregions: Vec<PortBarSubregionDefinition>,
81}
82
83#[derive(Clone)]
85pub struct PortBarSubregionDefinition {
86 pub kind: PortBarSubregionKind,
88 pub offset: u64,
90 pub size_bytes: u64,
92}
93
94#[derive(Copy, Clone, Eq, PartialEq, Debug)]
96pub enum PortBarSubregionKind {
97 CxlComponentRegisters,
99 MsiXTable,
101 MsiXPba,
103}
104
105pub(crate) fn build_cxl_register_locator_extended_capability(
106 register_bir: CxlRegisterLocatorRegisterBir,
107 register_block_offset: u64,
108) -> Option<Box<dyn PciExtendedCapability>> {
109 let locator = CxlRegisterLocatorDvsecExtendedCapability::new()
111 .with_register_block(
112 register_bir,
113 CxlRegisterLocatorRegisterBlockIdentifier::COMPONENT_REGISTERS,
114 register_block_offset,
115 )
116 .ok()?;
117
118 Some(Box::new(locator))
119}
120
121fn register_bir_from_bar_index(index: u8) -> Option<CxlRegisterLocatorRegisterBir> {
125 match index {
126 0 => Some(CxlRegisterLocatorRegisterBir::BAR_10H),
127 1 => Some(CxlRegisterLocatorRegisterBir::BAR_14H),
128 2 => Some(CxlRegisterLocatorRegisterBir::BAR_18H),
129 3 => Some(CxlRegisterLocatorRegisterBir::BAR_1CH),
130 4 => Some(CxlRegisterLocatorRegisterBir::BAR_20H),
131 5 => Some(CxlRegisterLocatorRegisterBir::BAR_24H),
132 _ => None,
133 }
134}
135
136fn cxl_register_locator_from_bar(
141 bar: Option<&PortBarDefinition>,
142) -> Option<(CxlRegisterLocatorRegisterBir, u64)> {
143 let bar_cfg = bar?;
144 let component_subregion = bar_cfg
145 .subregions
146 .iter()
147 .find(|region| region.kind == PortBarSubregionKind::CxlComponentRegisters)?;
148
149 let Some(register_bir) = register_bir_from_bar_index(bar_cfg.index) else {
150 tracelimit::warn_ratelimited!(
151 bar_index = bar_cfg.index,
152 "unsupported BAR index for CXL Register Locator"
153 );
154 return None;
155 };
156
157 Some((register_bir, component_subregion.offset))
158}
159
160fn has_cxl_component_register_subregion(bar: Option<&PortBarDefinition>) -> bool {
161 bar.is_some_and(|bar_cfg| {
162 bar_cfg
163 .subregions
164 .iter()
165 .any(|region| region.kind == PortBarSubregionKind::CxlComponentRegisters)
166 })
167}
168
169fn drop_cxl_component_register_subregions(bar: &mut Option<PortBarDefinition>) {
170 if let Some(bar_cfg) = bar {
171 bar_cfg
172 .subregions
173 .retain(|region| region.kind != PortBarSubregionKind::CxlComponentRegisters);
174
175 if bar_cfg.subregions.is_empty() {
176 *bar = None;
177 }
178 }
179}
180
181fn default_bar_from_settings(settings: &PciePortSettings) -> Option<PortBarDefinition> {
182 let cxl_requested = settings
183 .cxl_flex_bus_port_capability
184 .is_some_and(|capability| capability.cache_capable() || capability.mem_capable());
185
186 cxl_requested.then_some(PortBarDefinition {
187 index: 0,
188 size_bytes: CXL_COMPONENT_REGISTERS_SIZE_BYTES,
189 subregions: vec![PortBarSubregionDefinition {
190 kind: PortBarSubregionKind::CxlComponentRegisters,
191 offset: 0,
192 size_bytes: CXL_COMPONENT_REGISTERS_SIZE_BYTES,
193 }],
194 })
195}
196
197pub(crate) fn filter_acs_capabilities_for_bridge(
199 port_type: &DevicePortType,
200 requested: u16,
201) -> u16 {
202 let type_mask = match port_type {
203 DevicePortType::RootPort | DevicePortType::DownstreamSwitchPort => {
204 ACS_CAPABILITY_ALLOWED_ROOT_OR_DSP_MASK
205 }
206 DevicePortType::UpstreamSwitchPort => ACS_CAPABILITY_ALLOWED_USP_MASK,
207 DevicePortType::Endpoint => 0,
208 };
209
210 requested & ACS_CAPABILITY_IMPLEMENTED_MASK & type_mask
211}
212
213#[derive(Inspect)]
218pub struct PcieDownstreamPort {
219 pub name: String,
221
222 pub cfg_space: ConfigSpaceType1Emulator,
224
225 #[inspect(skip)]
227 pub link: Option<(Arc<str>, Box<dyn GenericPciBusDevice>)>,
228
229 #[inspect(skip)]
231 bar: Option<PortBarDefinition>,
232
233 #[inspect(skip)]
235 cxl_component_registers: Option<CxlComponentRegisters>,
236}
237
238impl PcieDownstreamPort {
239 pub fn new(
250 name: impl Into<String>,
251 hardware_ids: HardwareIds,
252 port_type: DevicePortType,
253 multi_function: bool,
254 hotplug_slot_number: Option<u32>,
255 msi_target: &MsiTarget,
256 settings: PciePortSettings,
257 register_mmio: Option<&mut dyn RegisterMmioIntercept>,
258 mut bar: Option<PortBarDefinition>,
259 ) -> Self {
260 let port_name = name.into();
261 let mut bars = DeviceBars::new();
262 let mut cxl_component_registers = None;
263 let mut cxl_locator_capability = None;
264 if bar.is_none() {
265 bar = default_bar_from_settings(&settings);
266 }
267
268 let mut cxl_enabled = settings
270 .cxl_flex_bus_port_capability
271 .is_some_and(|capability| capability.cache_capable() || capability.mem_capable());
272
273 let requested_cxl_component_subregion = has_cxl_component_register_subregion(bar.as_ref());
274
275 if cxl_enabled && requested_cxl_component_subregion {
276 if let Some((register_bir, register_block_offset)) =
277 cxl_register_locator_from_bar(bar.as_ref())
278 {
279 if let Some(locator_capability) = build_cxl_register_locator_extended_capability(
280 register_bir,
281 register_block_offset,
282 ) {
283 cxl_locator_capability = Some(locator_capability);
284 cxl_component_registers = Some(CxlComponentRegisters::new());
285 } else {
286 tracelimit::warn_ratelimited!(
287 offset = register_block_offset,
288 "invalid CXL Register Locator settings; disabling CXL BAR subregion"
289 );
290 drop_cxl_component_register_subregions(&mut bar);
291 cxl_enabled = false;
292 }
293 } else {
294 tracelimit::warn_ratelimited!(
295 "invalid CXL Register Locator BAR configuration; disabling CXL BAR subregion"
296 );
297 drop_cxl_component_register_subregions(&mut bar);
298 cxl_enabled = false;
299 }
300 }
301
302 if let Some(bar_cfg) = &bar {
304 if bar_cfg.index != 0 {
305 tracelimit::warn_ratelimited!(
306 bar_index = bar_cfg.index,
307 "ignoring unsupported BAR index; only BAR0 is supported"
308 );
309 bar = None;
310 } else if let Some(register_mmio) = register_mmio {
311 let region_name = format!("{}-bar{}", port_name, bar_cfg.index);
312 bars = bars.bar0(
313 bar_cfg.size_bytes,
314 BarMemoryKind::Intercept(
315 register_mmio.new_io_region(®ion_name, bar_cfg.size_bytes),
316 ),
317 );
318 } else {
319 tracelimit::warn_ratelimited!(
320 "ignoring BAR configuration because MMIO register context is missing"
321 );
322 bar = None;
323 }
324 }
325
326 if cxl_enabled && requested_cxl_component_subregion && bar.is_none() {
329 tracelimit::warn_ratelimited!(
330 "dropping CXL DVSECs because BAR MMIO interception is unavailable"
331 );
332 cxl_enabled = false;
333 cxl_locator_capability = None;
334 cxl_component_registers = None;
335 }
336
337 let (hotplug, slot_number) = match hotplug_slot_number {
338 Some(slot) => (true, Some(slot)),
339 None => (false, None),
340 };
341
342 let msi_capability = MsiCapability::new(0, true, false, msi_target);
343 let acs_supported =
344 filter_acs_capabilities_for_bridge(&port_type, settings.acs_capabilities_supported);
345
346 let pcie_cap = if hotplug {
347 let slot_num = slot_number.unwrap_or(0);
348 PciExpressCapability::new(port_type, None).with_hotplug_support(slot_num)
349 } else {
350 PciExpressCapability::new(port_type, None)
351 };
352
353 let extended_capabilities = if acs_supported != 0 {
354 vec![
355 Box::new(AcsExtendedCapability::with_capabilities(acs_supported))
356 as Box<dyn PciExtendedCapability>,
357 ]
358 } else {
359 vec![]
360 };
361
362 let mut extended_capabilities = extended_capabilities;
363
364 if cxl_enabled {
365 extended_capabilities.push(Box::new(CxlPortDvsecExtendedCapability::new()));
368 let mut flex_bus_dvsec = CxlFlexBusPortDvsecExtendedCapability::new();
369 if let Some(capability) = settings.cxl_flex_bus_port_capability {
370 flex_bus_dvsec = flex_bus_dvsec
371 .with_cache_capable(capability.cache_capable())
372 .with_mem_capable(capability.mem_capable())
373 .with_optional_capabilities(
374 capability.cache_capable(),
375 capability.cxl_68b_flit_and_vh_capable(),
376 capability.cxl_multi_logical_device_capable(),
377 capability.cxl_latency_optimized_256b_flit_capable(),
378 capability.cxl_pbr_flit_capable(),
379 );
380 }
381 extended_capabilities.push(Box::new(flex_bus_dvsec));
382
383 if let Some(locator_capability) = cxl_locator_capability {
384 extended_capabilities.push(locator_capability);
385 }
386 }
387
388 let cfg_space = ConfigSpaceType1Emulator::new_with_bars(
389 hardware_ids,
390 vec![Box::new(pcie_cap), Box::new(msi_capability)],
391 extended_capabilities,
392 bars,
393 )
394 .with_multi_function_bit(multi_function);
395
396 Self {
397 name: port_name,
398 cfg_space,
399 link: None,
400 bar,
401 cxl_component_registers,
402 }
403 }
404
405 pub(crate) fn find_bar(&self, addr: u64) -> Option<(u8, u64)> {
407 self.cfg_space.find_bar(addr)
408 }
409
410 pub(crate) fn save_cxl_component_registers_state(
414 &mut self,
415 ) -> Result<Option<CxlComponentRegistersSavedState>, SaveError> {
416 self.cxl_component_registers
417 .as_mut()
418 .map(|regs| regs.save())
419 .transpose()
420 }
421
422 pub(crate) fn restore_cxl_component_registers_state(
427 &mut self,
428 state: Option<CxlComponentRegistersSavedState>,
429 ) -> Result<(), RestoreError> {
430 match (&mut self.cxl_component_registers, state) {
431 (Some(current), Some(saved)) => current.restore(saved)?,
432 (None, None) => {}
433 (Some(_), None) => {
434 return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
435 "missing CXL component-register state"
436 )));
437 }
438 (None, Some(_)) => {
439 return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
440 "unexpected CXL component-register state"
441 )));
442 }
443 }
444
445 Ok(())
446 }
447
448 pub(crate) fn bar_mmio_read(&mut self, bar: u8, bar_offset: u64, data: &mut [u8]) -> IoResult {
453 if bar != 0 {
454 tracelimit::warn_ratelimited!(bar, "unsupported port BAR read");
455 data.fill(0xff);
456 return IoResult::Ok;
457 }
458
459 let Some(bar_cfg) = &self.bar else {
460 tracelimit::warn_ratelimited!(bar, "BAR read without BAR configuration");
461 data.fill(0xff);
462 return IoResult::Ok;
463 };
464
465 let access_end = match bar_offset.checked_add(data.len() as u64) {
466 Some(end) => end,
467 None => {
468 data.fill(0xff);
469 return IoResult::Ok;
470 }
471 };
472
473 let Some(subregion) = bar_cfg.subregions.iter().find(|subregion| {
474 let Some(subregion_end) = subregion.offset.checked_add(subregion.size_bytes) else {
475 return false;
476 };
477
478 bar_offset >= subregion.offset && access_end <= subregion_end
479 }) else {
480 tracelimit::warn_ratelimited!(
481 offset = bar_offset,
482 size = data.len(),
483 "BAR read outside configured subregions"
484 );
485 data.fill(0);
486 return IoResult::Ok;
487 };
488
489 match subregion.kind {
490 PortBarSubregionKind::CxlComponentRegisters => {
491 let Some(component_registers) = self.cxl_component_registers.as_ref() else {
492 tracelimit::warn_ratelimited!(
493 "CXL component register BAR read without component-register backing"
494 );
495 data.fill(0);
496 return IoResult::Ok;
497 };
498
499 let Some(relative_offset) = bar_offset.checked_sub(subregion.offset) else {
500 data.fill(0);
501 return IoResult::Ok;
502 };
503
504 if let Err(err) =
505 validate_cxl_component_register_access(relative_offset, data.len())
506 {
507 return IoResult::Err(err);
508 }
509
510 let Ok(relative_offset) = u16::try_from(relative_offset) else {
511 data.fill(0);
512 return IoResult::Ok;
513 };
514
515 match component_registers.read(relative_offset, data) {
516 IoResult::Err(IoError::InvalidRegister) => {
517 data.fill(0);
518 IoResult::Ok
519 }
520 res => res,
521 }
522 }
523 PortBarSubregionKind::MsiXTable | PortBarSubregionKind::MsiXPba => {
524 tracelimit::warn_ratelimited!(
526 "read BAR subregion of kind {:?}, offset 0x{:x}, size 0x{:x}",
527 subregion.kind,
528 subregion.offset,
529 subregion.size_bytes
530 );
531 data.fill(0);
532 IoResult::Ok
533 }
534 }
535 }
536
537 pub(crate) fn bar_mmio_write(&mut self, bar: u8, bar_offset: u64, data: &[u8]) -> IoResult {
542 if bar != 0 {
543 tracelimit::warn_ratelimited!(bar, "unsupported port BAR write");
544 return IoResult::Ok;
545 }
546
547 let Some(bar_cfg) = &self.bar else {
548 tracelimit::warn_ratelimited!(bar, "BAR write without BAR configuration");
549 return IoResult::Ok;
550 };
551
552 let access_end = match bar_offset.checked_add(data.len() as u64) {
553 Some(end) => end,
554 None => {
555 return IoResult::Ok;
556 }
557 };
558
559 let Some(subregion) = bar_cfg.subregions.iter().find(|subregion| {
560 let Some(subregion_end) = subregion.offset.checked_add(subregion.size_bytes) else {
561 return false;
562 };
563
564 bar_offset >= subregion.offset && access_end <= subregion_end
565 }) else {
566 tracelimit::warn_ratelimited!(
567 offset = bar_offset,
568 size = data.len(),
569 "BAR write outside configured subregions"
570 );
571 return IoResult::Ok;
572 };
573
574 match subregion.kind {
575 PortBarSubregionKind::CxlComponentRegisters => {
576 let Some(component_registers) = self.cxl_component_registers.as_mut() else {
577 tracelimit::warn_ratelimited!(
578 "CXL component register BAR write without component-register backing"
579 );
580 return IoResult::Ok;
581 };
582
583 let Some(relative_offset) = bar_offset.checked_sub(subregion.offset) else {
584 return IoResult::Ok;
585 };
586
587 if let Err(err) =
588 validate_cxl_component_register_access(relative_offset, data.len())
589 {
590 return IoResult::Err(err);
591 }
592
593 let Ok(relative_offset) = u16::try_from(relative_offset) else {
594 return IoResult::Ok;
595 };
596
597 match component_registers.write(relative_offset, data) {
598 IoResult::Err(IoError::InvalidRegister) => IoResult::Ok,
599 res => res,
600 }
601 }
602 PortBarSubregionKind::MsiXTable | PortBarSubregionKind::MsiXPba => {
603 tracelimit::warn_ratelimited!(
605 "write BAR subregion of kind {:?}, offset 0x{:x}, size 0x{:x}",
606 subregion.kind,
607 subregion.offset,
608 subregion.size_bytes
609 );
610 IoResult::Ok
611 }
612 }
613 }
614
615 pub fn bus_range(&self) -> AssignedBusRange {
620 self.cfg_space.bus_range()
621 }
622
623 fn fire_hotplug_msi(&self) {
629 let hotplug_enabled = self
630 .cfg_space
631 .capabilities()
632 .iter()
633 .find_map(|cap| cap.as_pci_express())
634 .is_some_and(|pcie| pcie.hot_plug_interrupt_enabled());
635
636 if hotplug_enabled {
637 if let Some(interrupt) = self
638 .cfg_space
639 .capabilities()
640 .iter()
641 .find_map(|cap| cap.as_msi_cap())
642 .and_then(|msi| msi.interrupt())
643 {
644 interrupt.deliver();
645 }
646 }
647 }
648
649 pub fn forward_cfg_read_with_routing(
652 &mut self,
653 bus: &u8,
654 function: &u8,
655 cfg_offset: u16,
656 value: &mut u32,
657 ) -> IoResult {
658 let bus_range = self.cfg_space.assigned_bus_range();
659
660 if bus_range == (0..=0) {
662 tracelimit::warn_ratelimited!("invalid access: port bus number range not configured");
663 return IoResult::Ok;
664 }
665
666 if bus_range.contains(bus) {
667 if let Some((_, device)) = &mut self.link {
668 let secondary_bus = *bus_range.start();
669 let result = device.pci_cfg_read_with_routing(
670 secondary_bus,
671 *bus,
672 *function,
673 cfg_offset,
674 value,
675 );
676
677 if let Some(result) = result {
678 match result {
679 IoResult::Ok => (),
680 res => return res,
681 }
682 }
683 } else if *bus != *bus_range.start() {
684 tracelimit::warn_ratelimited!(
685 "invalid access: bus number to access not within port's bus number range"
686 );
687 }
688 }
689
690 IoResult::Ok
691 }
692
693 pub fn forward_cfg_write_with_routing(
696 &mut self,
697 bus: &u8,
698 function: &u8,
699 cfg_offset: u16,
700 value: u32,
701 ) -> IoResult {
702 let bus_range = self.cfg_space.assigned_bus_range();
703
704 if bus_range == (0..=0) {
706 tracelimit::warn_ratelimited!("invalid access: port bus number range not configured");
707 return IoResult::Ok;
708 }
709
710 if bus_range.contains(bus) {
711 if let Some((_, device)) = &mut self.link {
712 let secondary_bus = *bus_range.start();
713 let result = device.pci_cfg_write_with_routing(
714 secondary_bus,
715 *bus,
716 *function,
717 cfg_offset,
718 value,
719 );
720
721 if let Some(result) = result {
722 match result {
723 IoResult::Ok => (),
724 res => return res,
725 }
726 }
727 } else if *bus != *bus_range.start() {
728 tracelimit::warn_ratelimited!(
729 "invalid access: bus number to access not within port's bus number range"
730 );
731 }
732 }
733
734 IoResult::Ok
735 }
736
737 pub fn add_pcie_device(
739 &mut self,
740 port_name: &str,
741 device_name: &str,
742 device: Box<dyn GenericPciBusDevice>,
743 ) -> anyhow::Result<()> {
744 if port_name == self.name.as_str() {
746 if self.link.is_some() {
748 bail!("port is already occupied");
749 }
750
751 self.link = Some((device_name.into(), device));
753
754 self.cfg_space.set_presence_detect_state(true);
756
757 return Ok(());
758 }
759
760 bail!("port name does not match")
762 }
763
764 pub fn hotplug_add_device(
769 &mut self,
770 device_name: &str,
771 device: Box<dyn GenericPciBusDevice>,
772 ) -> anyhow::Result<()> {
773 let is_hotplug_capable = self
774 .cfg_space
775 .capabilities()
776 .iter()
777 .find_map(|cap| cap.as_pci_express())
778 .is_some_and(|pcie| pcie.slot_capabilities().hot_plug_capable());
779
780 if !is_hotplug_capable {
781 bail!("port '{}' is not hotplug capable", self.name);
782 }
783 if self.link.is_some() {
784 bail!("port '{}' is already occupied", self.name);
785 }
786
787 self.link = Some((device_name.into(), device));
788
789 for cap in self.cfg_space.capabilities().iter() {
791 if let Some(pcie) = cap.as_pci_express() {
792 pcie.set_hotplug_state(true);
793 }
794 }
795 self.fire_hotplug_msi();
796 Ok(())
797 }
798
799 pub fn hotplug_remove_device(&mut self) -> anyhow::Result<()> {
801 let is_hotplug_capable = self
802 .cfg_space
803 .capabilities()
804 .iter()
805 .find_map(|cap| cap.as_pci_express())
806 .is_some_and(|pcie| pcie.slot_capabilities().hot_plug_capable());
807
808 if !is_hotplug_capable {
809 bail!("port '{}' is not hotplug capable", self.name);
810 }
811 if self.link.is_none() {
812 bail!("port '{}' is empty", self.name);
813 }
814
815 self.link = None;
816
817 for cap in self.cfg_space.capabilities().iter() {
819 if let Some(pcie) = cap.as_pci_express() {
820 pcie.set_hotplug_state(false);
821 }
822 }
823 self.fire_hotplug_msi();
824 Ok(())
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use crate::test_helpers::TestPcieMmioRegistration;
832 use chipset_device::io::IoResult;
833 use cxl_spec::pci_registers::spec::flex_bus_port_dvsec::CxlFlexBusPortDvsecCapability;
834 use parking_lot::Mutex;
835 use pci_bus::GenericPciBusDevice;
836 use pci_core::spec::hwid::HardwareIds;
837 use std::sync::Arc;
838
839 fn make_cxl_bar_port() -> PcieDownstreamPort {
840 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
841
842 let hardware_ids = HardwareIds {
843 vendor_id: 0x1234,
844 device_id: 0x5678,
845 revision_id: 0,
846 prog_if: ProgrammingInterface::NONE,
847 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
848 base_class: ClassCode::BRIDGE,
849 type0_sub_vendor_id: 0,
850 type0_sub_system_id: 0,
851 };
852
853 let mut mmio = TestPcieMmioRegistration {};
854 let msi_target = MsiTarget::disconnected();
855 PcieDownstreamPort::new(
856 "cxl-bar-port",
857 hardware_ids,
858 DevicePortType::RootPort,
859 false,
860 None,
861 &msi_target,
862 PciePortSettings {
863 acs_capabilities_supported: 0,
864 cxl_flex_bus_port_capability: Some(
865 CxlFlexBusPortDvsecCapability::new().with_mem_capable(true),
866 ),
867 },
868 Some(&mut mmio),
869 Some(PortBarDefinition {
870 index: 0,
871 size_bytes: 0x1000,
872 subregions: vec![PortBarSubregionDefinition {
873 kind: PortBarSubregionKind::CxlComponentRegisters,
874 offset: 0,
875 size_bytes: 0x1000,
876 }],
877 }),
878 )
879 }
880
881 struct MockDevice;
883
884 impl GenericPciBusDevice for MockDevice {
885 fn pci_cfg_read(&mut self, _offset: u16, _value: &mut u32) -> Option<IoResult> {
886 None
887 }
888
889 fn pci_cfg_write(&mut self, _offset: u16, _value: u32) -> Option<IoResult> {
890 None
891 }
892 }
893
894 #[derive(Default, Debug, Clone, PartialEq, Eq)]
895 struct RoutingStats {
896 direct_reads: usize,
897 forward_reads: Vec<(u8, u8, u16)>,
898 direct_writes: usize,
899 forward_writes: Vec<(u8, u8, u16, u32)>,
900 }
901
902 struct MultiFunctionMockDevice {
903 stats: Arc<Mutex<RoutingStats>>,
904 }
905
906 impl GenericPciBusDevice for MultiFunctionMockDevice {
907 fn pci_cfg_read(&mut self, _offset: u16, _value: &mut u32) -> Option<IoResult> {
908 self.stats.lock().direct_reads += 1;
909 Some(IoResult::Ok)
910 }
911
912 fn pci_cfg_write(&mut self, _offset: u16, _value: u32) -> Option<IoResult> {
913 self.stats.lock().direct_writes += 1;
914 Some(IoResult::Ok)
915 }
916
917 fn pci_cfg_read_with_routing(
918 &mut self,
919 _secondary_bus: u8,
920 target_bus: u8,
921 function: u8,
922 offset: u16,
923 value: &mut u32,
924 ) -> Option<IoResult> {
925 self.stats
926 .lock()
927 .forward_reads
928 .push((target_bus, function, offset));
929 *value = 0x1234_5678;
930 Some(IoResult::Ok)
931 }
932
933 fn pci_cfg_write_with_routing(
934 &mut self,
935 _secondary_bus: u8,
936 target_bus: u8,
937 function: u8,
938 offset: u16,
939 value: u32,
940 ) -> Option<IoResult> {
941 self.stats
942 .lock()
943 .forward_writes
944 .push((target_bus, function, offset, value));
945 Some(IoResult::Ok)
946 }
947 }
948
949 #[test]
950 fn test_add_pcie_device_sets_presence_detect_state() {
951 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
952
953 let hardware_ids = HardwareIds {
955 vendor_id: 0x1234,
956 device_id: 0x5678,
957 revision_id: 0,
958 prog_if: ProgrammingInterface::NONE,
959 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
960 base_class: ClassCode::BRIDGE,
961 type0_sub_vendor_id: 0,
962 type0_sub_system_id: 0,
963 };
964
965 let msi_conn = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
966 let mut port = PcieDownstreamPort::new(
967 "test-port",
968 hardware_ids,
969 DevicePortType::RootPort,
970 false,
971 Some(1), msi_conn.target(),
973 PciePortSettings::default(),
974 None,
975 None,
976 );
977
978 let mut slot_status_val = 0u32;
980 let result = port.cfg_space.read_u32(0x58, &mut slot_status_val); assert!(matches!(result, IoResult::Ok));
982 let initial_presence_detect = (slot_status_val >> 22) & 0x1; assert_eq!(
984 initial_presence_detect, 0,
985 "Initial presence detect state should be 0"
986 );
987
988 let mock_device = Box::new(MockDevice);
990 let result = port.add_pcie_device("test-port", "mock-device", mock_device);
991 assert!(result.is_ok(), "Adding device should succeed");
992
993 let result = port.cfg_space.read_u32(0x58, &mut slot_status_val);
995 assert!(matches!(result, IoResult::Ok));
996 let present_presence_detect = (slot_status_val >> 22) & 0x1;
997 assert_eq!(
998 present_presence_detect, 1,
999 "Presence detect state should be 1 after adding device"
1000 );
1001 }
1002
1003 #[test]
1004 fn test_add_pcie_device_without_hotplug() {
1005 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1006
1007 let hardware_ids = HardwareIds {
1009 vendor_id: 0x1234,
1010 device_id: 0x5678,
1011 revision_id: 0,
1012 prog_if: ProgrammingInterface::NONE,
1013 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1014 base_class: ClassCode::BRIDGE,
1015 type0_sub_vendor_id: 0,
1016 type0_sub_system_id: 0,
1017 };
1018
1019 let msi_conn = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1020 let mut port = PcieDownstreamPort::new(
1021 "test-port",
1022 hardware_ids,
1023 DevicePortType::RootPort,
1024 false,
1025 None, msi_conn.target(),
1027 PciePortSettings::default(),
1028 None,
1029 None,
1030 );
1031
1032 let mock_device = Box::new(MockDevice);
1034 let result = port.add_pcie_device("test-port", "mock-device", mock_device);
1035 assert!(
1036 result.is_ok(),
1037 "Adding device should succeed even without hotplug support"
1038 );
1039 }
1040
1041 #[test]
1042 fn test_direct_child_bus_reads_use_forward_for_multifunction_devices() {
1043 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1044
1045 let hardware_ids = HardwareIds {
1046 vendor_id: 0x1234,
1047 device_id: 0x5678,
1048 revision_id: 0,
1049 prog_if: ProgrammingInterface::NONE,
1050 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1051 base_class: ClassCode::BRIDGE,
1052 type0_sub_vendor_id: 0,
1053 type0_sub_system_id: 0,
1054 };
1055
1056 let msi_target = MsiTarget::disconnected();
1057 let mut port = PcieDownstreamPort::new(
1058 "test-port",
1059 hardware_ids,
1060 DevicePortType::RootPort,
1061 false,
1062 None,
1063 &msi_target,
1064 PciePortSettings::default(),
1065 None,
1066 None,
1067 );
1068
1069 port.cfg_space
1070 .write_u32(0x18, (1u32 << 16) | (1u32 << 8))
1071 .unwrap();
1072
1073 let stats = Arc::new(Mutex::new(RoutingStats::default()));
1074 port.link = Some((
1075 "mf-device".into(),
1076 Box::new(MultiFunctionMockDevice {
1077 stats: Arc::clone(&stats),
1078 }),
1079 ));
1080
1081 let mut value = 0;
1082 assert!(matches!(
1086 port.forward_cfg_read_with_routing(&1, &0, 0x10, &mut value),
1087 IoResult::Ok
1088 ));
1089 assert!(matches!(
1090 port.forward_cfg_read_with_routing(&1, &3, 0x14, &mut value),
1091 IoResult::Ok
1092 ));
1093
1094 let stats = stats.lock().clone();
1095 assert_eq!(stats.direct_reads, 0);
1096 assert_eq!(stats.forward_reads, vec![(1, 0, 0x10), (1, 3, 0x14)]);
1097 }
1098
1099 #[test]
1100 fn test_direct_child_bus_writes_use_forward_for_multifunction_devices() {
1101 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1102
1103 let hardware_ids = HardwareIds {
1104 vendor_id: 0x1234,
1105 device_id: 0x5678,
1106 revision_id: 0,
1107 prog_if: ProgrammingInterface::NONE,
1108 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1109 base_class: ClassCode::BRIDGE,
1110 type0_sub_vendor_id: 0,
1111 type0_sub_system_id: 0,
1112 };
1113
1114 let msi_conn = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1115 let mut port = PcieDownstreamPort::new(
1116 "test-port",
1117 hardware_ids,
1118 DevicePortType::RootPort,
1119 false,
1120 None,
1121 msi_conn.target(),
1122 PciePortSettings::default(),
1123 None,
1124 None,
1125 );
1126
1127 port.cfg_space
1128 .write_u32(0x18, (1u32 << 16) | (1u32 << 8))
1129 .unwrap();
1130
1131 let stats = Arc::new(Mutex::new(RoutingStats::default()));
1132 port.link = Some((
1133 "mf-device".into(),
1134 Box::new(MultiFunctionMockDevice {
1135 stats: Arc::clone(&stats),
1136 }),
1137 ));
1138
1139 assert!(matches!(
1143 port.forward_cfg_write_with_routing(&1, &0, 0x10, 0xAAAA_0000),
1144 IoResult::Ok
1145 ));
1146 assert!(matches!(
1147 port.forward_cfg_write_with_routing(&1, &2, 0x14, 0xBBBB_0000),
1148 IoResult::Ok
1149 ));
1150
1151 let stats = stats.lock().clone();
1152 assert_eq!(stats.direct_writes, 0);
1153 assert_eq!(
1154 stats.forward_writes,
1155 vec![(1, 0, 0x10, 0xAAAA_0000), (1, 2, 0x14, 0xBBBB_0000)]
1156 );
1157 }
1158
1159 #[test]
1160 fn test_port_cfg_space_save_restore() {
1161 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1162 use vmcore::save_restore::SaveRestore;
1163
1164 let hardware_ids = HardwareIds {
1165 vendor_id: 0x1234,
1166 device_id: 0x5678,
1167 revision_id: 0,
1168 prog_if: ProgrammingInterface::NONE,
1169 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1170 base_class: ClassCode::BRIDGE,
1171 type0_sub_vendor_id: 0,
1172 type0_sub_system_id: 0,
1173 };
1174
1175 let msi_conn = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1176 let mut port = PcieDownstreamPort::new(
1177 "test-port",
1178 hardware_ids,
1179 DevicePortType::RootPort,
1180 false,
1181 None,
1182 msi_conn.target(),
1183 PciePortSettings::default(),
1184 None,
1185 None,
1186 );
1187
1188 port.cfg_space.write_u32(0x18, 0x0012_1000).unwrap();
1190 assert_eq!(port.cfg_space.assigned_bus_range(), 0x10..=0x12);
1191
1192 let saved = port.cfg_space.save().expect("save should succeed");
1193
1194 port.cfg_space.write_u32(0x18, 0x0000_0000).unwrap();
1196 assert_eq!(port.cfg_space.assigned_bus_range(), 0..=0);
1197
1198 port.cfg_space
1199 .restore(saved)
1200 .expect("restore should succeed");
1201 assert_eq!(port.cfg_space.assigned_bus_range(), 0x10..=0x12);
1202 }
1203
1204 #[test]
1205 fn test_filter_acs_capabilities_for_bridge_type() {
1206 assert_eq!(
1207 filter_acs_capabilities_for_bridge(&DevicePortType::RootPort, 0x00ff),
1208 0x00df
1209 );
1210 assert_eq!(
1211 filter_acs_capabilities_for_bridge(&DevicePortType::DownstreamSwitchPort, 0x00ff),
1212 0x00df
1213 );
1214 assert_eq!(
1215 filter_acs_capabilities_for_bridge(&DevicePortType::UpstreamSwitchPort, 0x00ff),
1216 0
1217 );
1218 assert_eq!(
1219 filter_acs_capabilities_for_bridge(&DevicePortType::Endpoint, 0x00ff),
1220 0
1221 );
1222 }
1223
1224 #[test]
1225 fn test_root_port_adds_acs_only_when_non_zero() {
1226 use pci_core::spec::caps::ExtendedCapabilityId;
1227 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1228
1229 let hardware_ids = HardwareIds {
1230 vendor_id: 0x1234,
1231 device_id: 0x5678,
1232 revision_id: 0,
1233 prog_if: ProgrammingInterface::NONE,
1234 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1235 base_class: ClassCode::BRIDGE,
1236 type0_sub_vendor_id: 0,
1237 type0_sub_system_id: 0,
1238 };
1239
1240 let msi_target = MsiTarget::disconnected();
1241 let with_acs = PcieDownstreamPort::new(
1242 "with-acs",
1243 hardware_ids,
1244 DevicePortType::RootPort,
1245 false,
1246 None,
1247 &msi_target,
1248 PciePortSettings {
1249 acs_capabilities_supported: 0x005f,
1250 ..Default::default()
1251 },
1252 None,
1253 None,
1254 );
1255 let mut value = 0u32;
1256 with_acs.cfg_space.read_u32(0x100, &mut value).unwrap();
1257 assert_eq!(value & 0xffff, ExtendedCapabilityId::ACS.0 as u32);
1258
1259 let without_acs = PcieDownstreamPort::new(
1260 "without-acs",
1261 hardware_ids,
1262 DevicePortType::RootPort,
1263 false,
1264 None,
1265 &msi_target,
1266 PciePortSettings::default(),
1267 None,
1268 None,
1269 );
1270 without_acs.cfg_space.read_u32(0x100, &mut value).unwrap();
1271 assert_eq!(value, 0xffff_ffff);
1272 }
1273
1274 #[test]
1275 fn test_invalid_cxl_component_register_locator_disables_cxl_exposure() {
1276 use cxl_spec::pci_registers::spec::flex_bus_port_dvsec::CxlFlexBusPortDvsecCapability;
1277 use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1278
1279 let hardware_ids = HardwareIds {
1280 vendor_id: 0x1234,
1281 device_id: 0x5678,
1282 revision_id: 0,
1283 prog_if: ProgrammingInterface::NONE,
1284 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1285 base_class: ClassCode::BRIDGE,
1286 type0_sub_vendor_id: 0,
1287 type0_sub_system_id: 0,
1288 };
1289
1290 let msi_target = MsiTarget::disconnected();
1291 let port = PcieDownstreamPort::new(
1292 "test-port",
1293 hardware_ids,
1294 DevicePortType::RootPort,
1295 false,
1296 None,
1297 &msi_target,
1298 PciePortSettings {
1299 acs_capabilities_supported: 0,
1300 cxl_flex_bus_port_capability: Some(
1301 CxlFlexBusPortDvsecCapability::new().with_mem_capable(true),
1302 ),
1303 },
1304 None,
1305 Some(PortBarDefinition {
1306 index: 0,
1307 size_bytes: 0x1000,
1308 subregions: vec![PortBarSubregionDefinition {
1309 kind: PortBarSubregionKind::CxlComponentRegisters,
1310 offset: 0,
1311 size_bytes: 0x1000,
1312 }],
1313 }),
1314 );
1315
1316 let mut value = 0u32;
1317 port.cfg_space.read_u32(0x100, &mut value).unwrap();
1318 assert_eq!(
1319 value, 0xffff_ffff,
1320 "CXL DVSECs should be absent when CXL component-register BAR backing is invalid"
1321 );
1322 assert!(
1323 port.cxl_component_registers.is_none(),
1324 "component-register backing should not be allocated"
1325 );
1326 }
1327
1328 #[test]
1329 fn test_cxl_component_register_bar_rejects_1_or_2_byte_reads() {
1330 let mut port = make_cxl_bar_port();
1331
1332 let mut read1 = [0u8; 1];
1333 assert!(matches!(
1334 port.bar_mmio_read(0, 0, &mut read1),
1335 IoResult::Err(IoError::InvalidAccessSize)
1336 ));
1337
1338 let mut read2 = [0u8; 2];
1339 assert!(matches!(
1340 port.bar_mmio_read(0, 0, &mut read2),
1341 IoResult::Err(IoError::InvalidAccessSize)
1342 ));
1343 }
1344
1345 #[test]
1346 fn test_cxl_component_register_bar_rejects_1_or_2_byte_writes() {
1347 let mut port = make_cxl_bar_port();
1348
1349 let write1 = [0u8; 1];
1350 assert!(matches!(
1351 port.bar_mmio_write(0, 0, &write1),
1352 IoResult::Err(IoError::InvalidAccessSize)
1353 ));
1354
1355 let write2 = [0u8; 2];
1356 assert!(matches!(
1357 port.bar_mmio_write(0, 0, &write2),
1358 IoResult::Err(IoError::InvalidAccessSize)
1359 ));
1360 }
1361}