pcie/
root.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PCI Express root complex and root port emulation.
5
6use 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/// A generic PCI Express root complex emulator.
37#[derive(InspectMut)]
38pub struct GenericPcieRootComplex {
39    /// The lowest valid bus number under the root complex.
40    start_bus: u8,
41    /// The highest valid bus number under the root complex.
42    end_bus: u8,
43    /// Intercept control for the ECAM MMIO region.
44    ecam: Box<dyn ControlMmioIntercept>,
45    /// Map of root ports attached to the root complex, indexed by combined device and function numbers.
46    #[inspect(with = "|x| inspect::iter_by_key(x).map_value(|(_, v)| v)")]
47    ports: HashMap<u8, (Arc<str>, RootPort)>,
48}
49
50/// A description of a generic PCIe root port.
51pub struct GenericPcieRootPortDefinition {
52    /// The name of the root port.
53    pub name: Arc<str>,
54    /// Whether hotplug is enabled for this root port.
55    pub hotplug: bool,
56}
57
58/// A flat description of a PCIe switch without hierarchy.
59pub struct GenericSwitchDefinition {
60    /// The name of the switch.
61    pub name: Arc<str>,
62    /// Number of downstream ports.
63    pub num_downstream_ports: u8,
64    /// The parent port this switch is connected to.
65    pub parent_port: Arc<str>,
66    /// Whether hotplug is enabled for this switch.
67    pub hotplug: bool,
68}
69
70impl GenericSwitchDefinition {
71    /// Create a new switch definition.
72    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    /// Constructs a new `GenericPcieRootComplex` emulator.
96    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                // Use the device number as the slot number for hotpluggable ports
117                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    /// Attach the provided `GenericPciBusDevice` to the port identified.
136    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    /// Enumerate the downstream ports of the root complex.
165    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    /// Returns the size of the ECAM MMIO region this root complex is emulating.
176    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        // N.B. Emulators internally only support 4-byte aligned accesses to
278        // 4-byte registers, but the guest can use 1-, 2-, or 4 byte memory
279        // instructions to access ECAM. This function reads the 4-byte aligned
280        // value then shifts it around as needed before copying the data into
281        // the intercept completion bytes.
282
283        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        // N.B. Emulators internally only support 4-byte aligned accesses to
318        // 4-byte registers, but the guest can use 1-, 2-, or 4-byte memory
319        // instructions to access ECAM. If the guest is using a 1- or 2-byte
320        // instruction, this function reads the 4-byte aligned configuration
321        // register, masks in the new bytes being written by the guest, and
322        // uses the resulting value for write emulation.
323
324        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    /// The common PCIe port implementation.
372    #[inspect(flatten)]
373    port: PcieDownstreamPort,
374}
375
376impl RootPort {
377    /// Constructs a new [`RootPort`] emulator.
378    ///
379    /// # Arguments
380    /// * `name` - The name for this root port
381    /// * `hotplug_slot_number` - The slot number for hotplug support. `Some(slot_number)` enables hotplug, `None` disables it
382    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    /// Try to connect a PCIe device, returning an existing device name if the
407    /// port is already occupied.
408    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 the connection failed, it means the port is already occupied
420                // We need to get the name of the existing device
421                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                    // This shouldn't happen if add_pcie_device works correctly
431                    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        // Single bus
559        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        // Two bus
564        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        // Everything
569        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        // Pre-bus number assignment, random accesses return 1s.
645        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        // Secondary and suboordinate bus number registers are both
650        // read / write, defaulting to 0.
651        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        // Bus numbers assigned, but no endpoint attached yet.
670        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        // The secondary bus behind root port 0 has been assigned bus number
688        // 1, so now the attached endpoint is accessible.
689        rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
690        assert_eq!(value_32, 0xDEAD_BEEF);
691
692        // Reassign the secondary bus number to 2.
693        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        // The endpoint is no longer accessible at bus number 1, and is now
699        // accessible at bus number 2.
700        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        // Write the command register of both ports with a reasonable value.
718        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        // Reset the emulator, and ensure programming was cleared.
730        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        // Re-write the command register of both ports after reset.
739        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        // Test with hotplug disabled (None)
754        let root_port_no_hotplug = RootPort::new("test-port-no-hotplug", None);
755        // We can't easily verify hotplug is disabled without accessing internal state,
756        // but we can verify the port was created successfully
757        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        // Test with hotplug enabled (Some(slot_number))
767        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        // The slot number and hotplug capability would be tested via PCIe capability registers
776        // but that requires more complex setup
777    }
778
779    #[test]
780    fn test_root_port_invalid_bus_range_handling() {
781        let mut root_port = RootPort::new("test-port", None);
782
783        // Don't configure bus numbers, so the range should be 0..=0 (invalid)
784        let bus_range = root_port.port.cfg_space.assigned_bus_range();
785        assert_eq!(bus_range, 0..=0);
786
787        // Test that forwarding returns Ok but doesn't crash when bus range is invalid
788        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}