1use crate::BDF_BUS_SHIFT;
7use crate::BDF_DEVICE_FUNCTION_MASK;
8use crate::BDF_DEVICE_SHIFT;
9use crate::MAX_FUNCTIONS_PER_BUS;
10use crate::PAGE_OFFSET_MASK;
11use crate::PAGE_SHIFT;
12use crate::PAGE_SIZE64;
13use crate::ROOT_PORT_DEVICE_ID;
14use crate::VENDOR_ID;
15use crate::port::PcieDownstreamPort;
16use chipset_device::ChipsetDevice;
17use chipset_device::io::IoError;
18use chipset_device::io::IoResult;
19use chipset_device::mmio::ControlMmioIntercept;
20use chipset_device::mmio::MmioIntercept;
21use chipset_device::mmio::RegisterMmioIntercept;
22use inspect::Inspect;
23use inspect::InspectMut;
24use memory_range::MemoryRange;
25use pci_bus::GenericPciBusDevice;
26use pci_core::spec::caps::pci_express::DevicePortType;
27use pci_core::spec::hwid::ClassCode;
28use pci_core::spec::hwid::HardwareIds;
29use pci_core::spec::hwid::ProgrammingInterface;
30use pci_core::spec::hwid::Subclass;
31use std::collections::HashMap;
32use std::sync::Arc;
33use vmcore::device_state::ChangeDeviceState;
34use zerocopy::IntoBytes;
35
36#[derive(InspectMut)]
38pub struct GenericPcieRootComplex {
39 start_bus: u8,
41 end_bus: u8,
43 ecam: Box<dyn ControlMmioIntercept>,
45 #[inspect(with = "|x| inspect::iter_by_key(x).map_value(|(_, v)| v)")]
47 ports: HashMap<u8, (Arc<str>, RootPort)>,
48}
49
50pub struct GenericPcieRootPortDefinition {
52 pub name: Arc<str>,
54 pub hotplug: bool,
56}
57
58pub struct GenericSwitchDefinition {
60 pub name: Arc<str>,
62 pub num_downstream_ports: u8,
64 pub parent_port: Arc<str>,
66 pub hotplug: bool,
68}
69
70impl GenericSwitchDefinition {
71 pub fn new(
73 name: impl Into<Arc<str>>,
74 num_downstream_ports: u8,
75 parent_port: impl Into<Arc<str>>,
76 hotplug: bool,
77 ) -> Self {
78 Self {
79 name: name.into(),
80 num_downstream_ports,
81 parent_port: parent_port.into(),
82 hotplug,
83 }
84 }
85}
86
87enum DecodedEcamAccess<'a> {
88 UnexpectedIntercept,
89 Unroutable,
90 InternalBus(&'a mut RootPort, u16),
91 DownstreamPort(&'a mut RootPort, u8, u8, u16),
92}
93
94impl GenericPcieRootComplex {
95 pub fn new(
97 register_mmio: &mut dyn RegisterMmioIntercept,
98 start_bus: u8,
99 end_bus: u8,
100 ecam_range: MemoryRange,
101 ports: Vec<GenericPcieRootPortDefinition>,
102 ) -> Self {
103 assert_eq!(
104 ecam_size_from_bus_numbers(start_bus, end_bus),
105 ecam_range.len()
106 );
107
108 let mut ecam = register_mmio.new_io_region("ecam", ecam_range.len());
109 ecam.map(ecam_range.start());
110
111 let port_map: HashMap<u8, (Arc<str>, RootPort)> = ports
112 .into_iter()
113 .enumerate()
114 .map(|(i, definition)| {
115 let device_number: u8 = (i << BDF_DEVICE_SHIFT).try_into().expect("too many ports");
116 let hotplug_slot_number = if definition.hotplug {
118 Some((device_number as u32) + 1)
119 } else {
120 None
121 };
122 let root_port = RootPort::new(definition.name.clone(), hotplug_slot_number);
123 (device_number, (definition.name, root_port))
124 })
125 .collect();
126
127 Self {
128 start_bus,
129 end_bus,
130 ecam,
131 ports: port_map,
132 }
133 }
134
135 pub fn add_pcie_device(
137 &mut self,
138 port: u8,
139 name: impl AsRef<str>,
140 dev: Box<dyn GenericPciBusDevice>,
141 ) -> Result<(), Arc<str>> {
142 let (_port_name, root_port) = self.ports.get_mut(&port).ok_or_else(|| -> Arc<str> {
143 tracing::error!(
144 "GenericPcieRootComplex: port {:#x} not found for device '{}'",
145 port,
146 name.as_ref()
147 );
148 format!("Port {:#x} not found", port).into()
149 })?;
150
151 match root_port.connect_device(name, dev) {
152 Ok(()) => Ok(()),
153 Err(existing_device) => {
154 tracing::warn!(
155 "GenericPcieRootComplex: failed to connect device to port {:#x}, existing device: '{}'",
156 port,
157 existing_device
158 );
159 Err(existing_device)
160 }
161 }
162 }
163
164 pub fn downstream_ports(&self) -> Vec<(u8, Arc<str>)> {
166 let ports: Vec<(u8, Arc<str>)> = self
167 .ports
168 .iter()
169 .map(|(port, (name, _))| (*port, name.clone()))
170 .collect();
171
172 ports
173 }
174
175 pub fn ecam_size(&self) -> u64 {
177 ecam_size_from_bus_numbers(self.start_bus, self.end_bus)
178 }
179
180 fn decode_ecam_access<'a>(&'a mut self, addr: u64) -> DecodedEcamAccess<'a> {
181 let ecam_offset = match self.ecam.offset_of(addr) {
182 Some(offset) => offset,
183 None => {
184 return DecodedEcamAccess::UnexpectedIntercept;
185 }
186 };
187
188 let ecam_based_bdf = (ecam_offset >> PAGE_SHIFT) as u16;
189 let bus_number = ((ecam_based_bdf >> BDF_BUS_SHIFT) as u8) + self.start_bus;
190 let device_function = (ecam_based_bdf & BDF_DEVICE_FUNCTION_MASK) as u8;
191 let cfg_offset_within_function = (ecam_offset & PAGE_OFFSET_MASK) as u16;
192
193 if bus_number == self.start_bus {
194 match self.ports.get_mut(&device_function) {
195 Some((_, port)) => {
196 return DecodedEcamAccess::InternalBus(port, cfg_offset_within_function);
197 }
198 None => return DecodedEcamAccess::Unroutable,
199 }
200 } else if bus_number > self.start_bus && bus_number <= self.end_bus {
201 for (_, port) in self.ports.values_mut() {
202 if port
203 .port
204 .cfg_space
205 .assigned_bus_range()
206 .contains(&bus_number)
207 {
208 return DecodedEcamAccess::DownstreamPort(
209 port,
210 bus_number,
211 device_function,
212 cfg_offset_within_function,
213 );
214 }
215 }
216 return DecodedEcamAccess::Unroutable;
217 }
218
219 DecodedEcamAccess::UnexpectedIntercept
220 }
221}
222
223fn ecam_size_from_bus_numbers(start_bus: u8, end_bus: u8) -> u64 {
224 assert!(end_bus >= start_bus);
225 let bus_count = (end_bus as u16) - (start_bus as u16) + 1;
226 (bus_count as u64) * (MAX_FUNCTIONS_PER_BUS as u64) * PAGE_SIZE64
227}
228
229impl ChangeDeviceState for GenericPcieRootComplex {
230 fn start(&mut self) {}
231
232 async fn stop(&mut self) {}
233
234 async fn reset(&mut self) {
235 for (_, (_, port)) in self.ports.iter_mut() {
236 port.port.cfg_space.reset();
237 }
238 }
239}
240
241impl ChipsetDevice for GenericPcieRootComplex {
242 fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
243 Some(self)
244 }
245}
246
247macro_rules! validate_ecam_intercept {
248 ($address:ident, $data:ident) => {
249 if !matches!($data.len(), 1 | 2 | 4) {
250 return IoResult::Err(IoError::InvalidAccessSize);
251 }
252
253 if !((($data.len() == 4) && ($address & 3 == 0))
254 || (($data.len() == 2) && ($address & 1 == 0))
255 || ($data.len() == 1))
256 {
257 return IoResult::Err(IoError::UnalignedAccess);
258 }
259 };
260}
261
262macro_rules! check_result {
263 ($result:expr) => {
264 match $result {
265 IoResult::Ok => (),
266 res => {
267 return res;
268 }
269 }
270 };
271}
272
273impl MmioIntercept for GenericPcieRootComplex {
274 fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
275 validate_ecam_intercept!(addr, data);
276
277 let dword_aligned_addr = addr & !3;
284 let mut dword_value = !0;
285 match self.decode_ecam_access(dword_aligned_addr) {
286 DecodedEcamAccess::UnexpectedIntercept => {
287 tracing::error!("unexpected intercept at address 0x{:16x}", addr);
288 }
289 DecodedEcamAccess::Unroutable => {
290 tracelimit::warn_ratelimited!("unroutable config space access");
291 }
292 DecodedEcamAccess::InternalBus(port, cfg_offset) => {
293 check_result!(port.port.cfg_space.read_u32(cfg_offset, &mut dword_value));
294 }
295 DecodedEcamAccess::DownstreamPort(port, bus_number, device_function, cfg_offset) => {
296 check_result!(port.forward_cfg_read(
297 &bus_number,
298 &device_function,
299 cfg_offset & !3,
300 &mut dword_value,
301 ));
302 }
303 }
304
305 let byte_offset_within_dword = (addr & 3) as usize;
306 data.copy_from_slice(
307 &dword_value.as_bytes()
308 [byte_offset_within_dword..byte_offset_within_dword + data.len()],
309 );
310
311 IoResult::Ok
312 }
313
314 fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
315 validate_ecam_intercept!(addr, data);
316
317 let dword_aligned_addr = addr & !3;
325 let write_dword = match data.len() {
326 4 => {
327 let mut temp: u32 = 0;
328 temp.as_mut_bytes().copy_from_slice(data);
329 temp
330 }
331 _ => {
332 let mut temp_bytes: [u8; 4] = [0, 0, 0, 0];
333 check_result!(self.mmio_read(dword_aligned_addr, &mut temp_bytes));
334
335 let byte_offset_within_dword = (addr & 3) as usize;
336 temp_bytes[byte_offset_within_dword..byte_offset_within_dword + data.len()]
337 .copy_from_slice(data);
338
339 let mut temp: u32 = 0;
340 temp.as_mut_bytes().copy_from_slice(&temp_bytes);
341 temp
342 }
343 };
344
345 match self.decode_ecam_access(dword_aligned_addr) {
346 DecodedEcamAccess::UnexpectedIntercept => {
347 tracing::error!("unexpected intercept at address 0x{:16x}", addr);
348 }
349 DecodedEcamAccess::Unroutable => {
350 tracelimit::warn_ratelimited!("unroutable config space access");
351 }
352 DecodedEcamAccess::InternalBus(port, cfg_offset) => {
353 check_result!(port.port.cfg_space.write_u32(cfg_offset, write_dword));
354 }
355 DecodedEcamAccess::DownstreamPort(port, bus_number, device_function, cfg_offset) => {
356 check_result!(port.forward_cfg_write(
357 &bus_number,
358 &device_function,
359 cfg_offset,
360 write_dword,
361 ));
362 }
363 }
364
365 IoResult::Ok
366 }
367}
368
369#[derive(Inspect)]
370struct RootPort {
371 #[inspect(flatten)]
373 port: PcieDownstreamPort,
374}
375
376impl RootPort {
377 pub fn new(name: impl Into<Arc<str>>, hotplug_slot_number: Option<u32>) -> Self {
383 let name_str = name.into();
384 let hardware_ids = HardwareIds {
385 vendor_id: VENDOR_ID,
386 device_id: ROOT_PORT_DEVICE_ID,
387 revision_id: 0,
388 prog_if: ProgrammingInterface::NONE,
389 sub_class: Subclass::BRIDGE_PCI_TO_PCI,
390 base_class: ClassCode::BRIDGE,
391 type0_sub_vendor_id: 0,
392 type0_sub_system_id: 0,
393 };
394
395 let port = PcieDownstreamPort::new(
396 name_str.to_string(),
397 hardware_ids,
398 DevicePortType::RootPort,
399 false,
400 hotplug_slot_number,
401 );
402
403 Self { port }
404 }
405
406 fn connect_device(
409 &mut self,
410 name: impl AsRef<str>,
411 dev: Box<dyn GenericPciBusDevice>,
412 ) -> Result<(), Arc<str>> {
413 let device_name = name.as_ref();
414 let port_name = self.port.name.clone();
415
416 match self.port.add_pcie_device(&port_name, device_name, dev) {
417 Ok(()) => Ok(()),
418 Err(_error) => {
419 if let Some((existing_name, _)) = &self.port.link {
422 tracing::warn!(
423 "RootPort: '{}' failed to connect device '{}', port already occupied by '{}'",
424 port_name,
425 device_name,
426 existing_name
427 );
428 Err(existing_name.clone())
429 } else {
430 tracing::error!(
432 "RootPort: '{}' connection failed for device '{}' but no existing device found",
433 port_name,
434 device_name
435 );
436 panic!("Port connection failed but no existing device found")
437 }
438 }
439 }
440 }
441
442 fn forward_cfg_read(
443 &mut self,
444 bus: &u8,
445 device_function: &u8,
446 cfg_offset: u16,
447 value: &mut u32,
448 ) -> IoResult {
449 self.port
450 .forward_cfg_read_with_routing(bus, device_function, cfg_offset, value)
451 }
452
453 fn forward_cfg_write(
454 &mut self,
455 bus: &u8,
456 device_function: &u8,
457 cfg_offset: u16,
458 value: u32,
459 ) -> IoResult {
460 self.port
461 .forward_cfg_write_with_routing(bus, device_function, cfg_offset, value)
462 }
463}
464
465mod save_restore {
466 use super::*;
467 use vmcore::save_restore::SaveError;
468 use vmcore::save_restore::SaveRestore;
469 use vmcore::save_restore::SavedStateNotSupported;
470
471 impl SaveRestore for GenericPcieRootComplex {
472 type SavedState = SavedStateNotSupported;
473
474 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
475 Err(SaveError::NotSupported)
476 }
477
478 fn restore(
479 &mut self,
480 state: Self::SavedState,
481 ) -> Result<(), vmcore::save_restore::RestoreError> {
482 match state {}
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::test_helpers::*;
491 use pal_async::async_test;
492
493 fn instantiate_root_complex(
494 start_bus: u8,
495 end_bus: u8,
496 port_count: u8,
497 ) -> GenericPcieRootComplex {
498 let port_defs = (0..port_count)
499 .map(|i| GenericPcieRootPortDefinition {
500 name: format!("test-port-{}", i).into(),
501 hotplug: false,
502 })
503 .collect();
504
505 let mut register_mmio = TestPcieMmioRegistration {};
506 let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(start_bus, end_bus));
507 GenericPcieRootComplex::new(&mut register_mmio, start_bus, end_bus, ecam, port_defs)
508 }
509
510 #[test]
511 fn test_create() {
512 assert_eq!(
513 instantiate_root_complex(0, 0, 1).downstream_ports().len(),
514 1
515 );
516 assert_eq!(
517 instantiate_root_complex(0, 1, 1).downstream_ports().len(),
518 1
519 );
520 assert_eq!(
521 instantiate_root_complex(1, 1, 1).downstream_ports().len(),
522 1
523 );
524 assert_eq!(
525 instantiate_root_complex(255, 255, 1)
526 .downstream_ports()
527 .len(),
528 1
529 );
530
531 assert_eq!(
532 instantiate_root_complex(0, 0, 4).downstream_ports().len(),
533 4
534 );
535
536 assert_eq!(
537 instantiate_root_complex(0, 255, 32)
538 .downstream_ports()
539 .len(),
540 32
541 );
542 assert_eq!(
543 instantiate_root_complex(32, 32, 32)
544 .downstream_ports()
545 .len(),
546 32
547 );
548 assert_eq!(
549 instantiate_root_complex(255, 255, 32)
550 .downstream_ports()
551 .len(),
552 32
553 );
554 }
555
556 #[test]
557 fn test_ecam_size() {
558 assert_eq!(instantiate_root_complex(0, 0, 0).ecam_size(), 0x10_0000);
560 assert_eq!(instantiate_root_complex(32, 32, 0).ecam_size(), 0x10_0000);
561 assert_eq!(instantiate_root_complex(255, 255, 0).ecam_size(), 0x10_0000);
562
563 assert_eq!(instantiate_root_complex(0, 1, 0).ecam_size(), 0x20_0000);
565 assert_eq!(instantiate_root_complex(32, 33, 0).ecam_size(), 0x20_0000);
566 assert_eq!(instantiate_root_complex(254, 255, 0).ecam_size(), 0x20_0000);
567
568 assert_eq!(instantiate_root_complex(0, 255, 0).ecam_size(), 0x1000_0000);
570 }
571
572 #[test]
573 fn test_probe_ports_via_config_space() {
574 let mut rc = instantiate_root_complex(0, 255, 4);
575 for device_number in 0..4 {
576 let mut vendor_device: u32 = 0;
577 rc.mmio_read((device_number << 3) * 4096, vendor_device.as_mut_bytes())
578 .unwrap();
579 assert_eq!(vendor_device, 0xC030_1414);
580
581 let mut value_16: u16 = 0;
582 rc.mmio_read((device_number << 3) * 4096, value_16.as_mut_bytes())
583 .unwrap();
584 assert_eq!(value_16, 0x1414);
585
586 rc.mmio_read((device_number << 3) * 4096 + 2, value_16.as_mut_bytes())
587 .unwrap();
588 assert_eq!(value_16, 0xC030);
589 }
590
591 for device_number in 4..10 {
592 let mut value_32: u32 = 0;
593 rc.mmio_read((device_number << 3) * 4096, value_32.as_mut_bytes())
594 .unwrap();
595 assert_eq!(value_32, 0xFFFF_FFFF);
596
597 let mut value_16: u16 = 0;
598 rc.mmio_read((device_number << 3) * 4096, value_16.as_mut_bytes())
599 .unwrap();
600 assert_eq!(value_16, 0xFFFF);
601 rc.mmio_read((device_number << 3) * 4096 + 2, value_16.as_mut_bytes())
602 .unwrap();
603 assert_eq!(value_16, 0xFFFF);
604 }
605 }
606
607 #[test]
608 fn test_add_downstream_device_to_port() {
609 let mut rc = instantiate_root_complex(0, 0, 1);
610
611 let endpoint1 = TestPcieEndpoint::new(
612 |offset, value| match offset {
613 0x0 => {
614 *value = 0xAAAA_AAAA;
615 Some(IoResult::Ok)
616 }
617 _ => Some(IoResult::Err(IoError::InvalidRegister)),
618 },
619 |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
620 );
621
622 let endpoint2 = TestPcieEndpoint::new(
623 |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
624 |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
625 );
626
627 rc.add_pcie_device(0, "ep1", Box::new(endpoint1)).unwrap();
628
629 match rc.add_pcie_device(0, "ep2", Box::new(endpoint2)) {
630 Ok(()) => panic!("should have failed"),
631 Err(name) => {
632 assert_eq!(name, "ep1".into());
633 }
634 }
635 }
636
637 #[test]
638 fn test_root_port_cfg_forwarding() {
639 const SECONDARY_BUS_NUM_REG: u64 = 0x19;
640 const SUBOORDINATE_BUS_NUM_REG: u64 = 0x1A;
641
642 let mut rc = instantiate_root_complex(0, 255, 1);
643
644 let mut value_32: u32 = 0;
646 rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
647 assert_eq!(value_32, 0xFFFF_FFFF);
648
649 let mut bus_number: u8 = 0xFF;
652 rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
653 .unwrap();
654 assert_eq!(bus_number, 0);
655 rc.mmio_read(SUBOORDINATE_BUS_NUM_REG, bus_number.as_mut_bytes())
656 .unwrap();
657 assert_eq!(bus_number, 0);
658
659 rc.mmio_write(SECONDARY_BUS_NUM_REG, &[1]).unwrap();
660 rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
661 .unwrap();
662 assert_eq!(bus_number, 1);
663
664 rc.mmio_write(SUBOORDINATE_BUS_NUM_REG, &[2]).unwrap();
665 rc.mmio_read(SUBOORDINATE_BUS_NUM_REG, bus_number.as_mut_bytes())
666 .unwrap();
667 assert_eq!(bus_number, 2);
668
669 rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
671 assert_eq!(value_32, 0xFFFF_FFFF);
672
673 let endpoint = TestPcieEndpoint::new(
674 |offset, value| match offset {
675 0x0 => {
676 *value = 0xDEAD_BEEF;
677 Some(IoResult::Ok)
678 }
679 _ => Some(IoResult::Err(IoError::InvalidRegister)),
680 },
681 |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
682 );
683
684 rc.add_pcie_device(0, "test-ep", Box::new(endpoint))
685 .unwrap();
686
687 rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
690 assert_eq!(value_32, 0xDEAD_BEEF);
691
692 rc.mmio_write(SECONDARY_BUS_NUM_REG, &[2]).unwrap();
694 rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
695 .unwrap();
696 assert_eq!(bus_number, 2);
697
698 rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
701 assert_eq!(value_32, 0xFFFF_FFFF);
702 rc.mmio_read(2 * 256 * 4096, value_32.as_mut_bytes())
703 .unwrap();
704 assert_eq!(value_32, 0xDEAD_BEEF);
705 }
706
707 #[async_test]
708 async fn test_reset() {
709 const COMMAND_REG: u64 = 0x4;
710 const COMMAND_REG_VALUE: u16 = 0x0004;
711 const PORT0_ECAM: u64 = 0;
712 const PORT1_ECAM: u64 = (1 << 3) * 4096;
713
714 let mut rc = instantiate_root_complex(0, 255, 2);
715 let mut value_16: u16 = 0;
716
717 rc.mmio_write(PORT0_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
719 .unwrap();
720 rc.mmio_write(PORT1_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
721 .unwrap();
722 rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
723 .unwrap();
724 assert_eq!(value_16, COMMAND_REG_VALUE);
725 rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
726 .unwrap();
727 assert_eq!(value_16, COMMAND_REG_VALUE);
728
729 rc.reset().await;
731 rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
732 .unwrap();
733 assert_eq!(value_16, 0);
734 rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
735 .unwrap();
736 assert_eq!(value_16, 0);
737
738 rc.mmio_write(PORT0_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
740 .unwrap();
741 rc.mmio_write(PORT1_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
742 .unwrap();
743 rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
744 .unwrap();
745 assert_eq!(value_16, COMMAND_REG_VALUE);
746 rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
747 .unwrap();
748 assert_eq!(value_16, COMMAND_REG_VALUE);
749 }
750
751 #[test]
752 fn test_root_port_hotplug_options() {
753 let root_port_no_hotplug = RootPort::new("test-port-no-hotplug", None);
755 let mut vendor_device_id: u32 = 0;
758 root_port_no_hotplug
759 .port
760 .cfg_space
761 .read_u32(0x0, &mut vendor_device_id)
762 .unwrap();
763 let expected = (ROOT_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
764 assert_eq!(vendor_device_id, expected);
765
766 let root_port_with_hotplug = RootPort::new("test-port-hotplug", Some(5));
768 let mut vendor_device_id_hotplug: u32 = 0;
769 root_port_with_hotplug
770 .port
771 .cfg_space
772 .read_u32(0x0, &mut vendor_device_id_hotplug)
773 .unwrap();
774 assert_eq!(vendor_device_id_hotplug, expected);
775 }
778
779 #[test]
780 fn test_root_port_invalid_bus_range_handling() {
781 let mut root_port = RootPort::new("test-port", None);
782
783 let bus_range = root_port.port.cfg_space.assigned_bus_range();
785 assert_eq!(bus_range, 0..=0);
786
787 let mut value = 0u32;
789 let result = root_port
790 .port
791 .forward_cfg_read_with_routing(&1, &0, 0x0, &mut value);
792 assert!(matches!(result, IoResult::Ok));
793
794 let result = root_port
795 .port
796 .forward_cfg_write_with_routing(&1, &0, 0x0, value);
797 assert!(matches!(result, IoResult::Ok));
798 }
799}