Skip to main content

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 crate::port::PciePortSettings;
17use chipset_device::ChipsetDevice;
18use chipset_device::io::IoError;
19use chipset_device::io::IoResult;
20use chipset_device::mmio::ControlMmioIntercept;
21use chipset_device::mmio::MmioIntercept;
22use chipset_device::mmio::RegisterMmioIntercept;
23use cxl_spec::CxlComponentRegisters;
24use inspect::Inspect;
25use inspect::InspectMut;
26use memory_range::MemoryRange;
27use pci_bus::GenericPciBusDevice;
28use pci_core::bus_range::AssignedBusRange;
29use pci_core::msi::MsiTarget;
30use pci_core::spec::caps::pci_express::DevicePortType;
31use pci_core::spec::hwid::ClassCode;
32use pci_core::spec::hwid::HardwareIds;
33use pci_core::spec::hwid::ProgrammingInterface;
34use pci_core::spec::hwid::Subclass;
35use std::ops::RangeInclusive;
36use std::sync::Arc;
37use thiserror::Error;
38use vmcore::device_state::ChangeDeviceState;
39use zerocopy::IntoBytes;
40
41/// Error returned when a root complex configuration is invalid.
42#[derive(Debug, Error)]
43#[error("requested {port_count} root ports, but only {max} are supported")]
44pub struct InvalidRootComplexError {
45    port_count: usize,
46    max: usize,
47}
48
49/// A generic PCI Express root complex emulator.
50#[derive(InspectMut)]
51pub struct GenericPcieRootComplex {
52    /// The lowest valid bus number under the root complex.
53    start_bus: u8,
54    /// The highest valid bus number under the root complex.
55    end_bus: u8,
56    /// Intercept control for the ECAM MMIO region.
57    ecam: Box<dyn ControlMmioIntercept>,
58    /// Intercept control for the CHBCR MMIO region, when present.
59    chbcr: Option<Box<dyn ControlMmioIntercept>>,
60    /// CXL Component Registers backing CHBCR accesses in CXL mode.
61    cxl_component_registers: Option<CxlComponentRegisters>,
62    /// Devices on the root complex bus, sorted by devfn
63    /// (device << 3 | function).
64    #[inspect(with = "|x| inspect::iter_by_key(x.iter().map(|(k, v)| (k, v)))")]
65    devices: Vec<(u8, BusDevice)>,
66}
67
68/// A device occupying a slot on the root complex bus.
69enum BusDevice {
70    /// A root port providing downstream connectivity.
71    RootPort { name: Arc<str>, port: Box<RootPort> },
72    /// A Root Complex Integrated Endpoint (RCiEP).
73    Rciep {
74        name: Arc<str>,
75        dev: Box<dyn GenericPciBusDevice>,
76    },
77}
78
79impl Inspect for BusDevice {
80    fn inspect(&self, req: inspect::Request<'_>) {
81        match self {
82            BusDevice::RootPort { port, .. } => {
83                port.as_ref().inspect(req);
84            }
85            BusDevice::Rciep { name, .. } => {
86                req.value(name.as_ref());
87            }
88        }
89    }
90}
91
92/// Information about a downstream port in a PCIe topology.
93pub struct DownstreamPortInfo {
94    /// The devfn (device << 3 | function) of this port on the root complex bus.
95    pub devfn: u8,
96    /// The port name.
97    pub name: Arc<str>,
98    /// Shared bus range, updated by the config space emulator when the
99    /// guest programs secondary/subordinate bus numbers.
100    pub bus_range: AssignedBusRange,
101}
102
103/// A description of a generic PCIe root port.
104pub struct GenericPcieRootPortDefinition {
105    /// The name of the root port.
106    pub name: Arc<str>,
107    /// Whether hotplug is enabled for this root port.
108    pub hotplug: bool,
109    /// Express-level port settings (ACS, etc.).
110    pub settings: PciePortSettings,
111}
112
113/// A flat description of a PCIe switch without hierarchy.
114pub struct GenericSwitchDefinition {
115    /// The name of the switch.
116    pub name: Arc<str>,
117    /// Number of downstream ports.
118    pub num_downstream_ports: u8,
119    /// The parent port this switch is connected to.
120    pub parent_port: Arc<str>,
121    /// Whether hotplug is enabled for this switch.
122    pub hotplug: bool,
123    /// Express-level settings for downstream switch ports.
124    pub dsp_settings: PciePortSettings,
125}
126
127impl GenericSwitchDefinition {
128    /// Create a new switch definition.
129    pub fn new(
130        name: impl Into<Arc<str>>,
131        num_downstream_ports: u8,
132        parent_port: impl Into<Arc<str>>,
133        hotplug: bool,
134        dsp_settings: PciePortSettings,
135    ) -> Self {
136        Self {
137            name: name.into(),
138            num_downstream_ports,
139            parent_port: parent_port.into(),
140            hotplug,
141            dsp_settings,
142        }
143    }
144}
145
146enum DecodedEcamAccess<'a> {
147    UnexpectedIntercept,
148    Unroutable,
149    InternalBus(&'a mut RootPort, u16),
150    DownstreamPort(&'a mut RootPort, u8, u8, u16),
151    /// A Root Complex Integrated Endpoint (RCiEP) on the start bus.
152    /// Fields: device, function, config offset.
153    Rciep(&'a mut dyn GenericPciBusDevice, u8, u16),
154}
155
156/// Builder for [`GenericPcieRootComplex`].
157///
158/// Obtain via [`GenericPcieRootComplex::builder`], configure optional
159/// settings, then call [`build`](GenericPcieRootComplexBuilder::build).
160pub struct GenericPcieRootComplexBuilder<'a> {
161    register_mmio: &'a mut dyn RegisterMmioIntercept,
162    bus_range: RangeInclusive<u8>,
163    ecam_range: MemoryRange,
164    root_ports: Option<(Vec<GenericPcieRootPortDefinition>, &'a MsiTarget)>,
165    first_port_device_number: u8,
166    chbcr_range: Option<MemoryRange>,
167}
168
169impl<'a> GenericPcieRootComplexBuilder<'a> {
170    /// Add root ports to the complex.
171    ///
172    /// `msi_target` is the MSI target for all root ports; the caller is
173    /// responsible for creating the `MsiConnection` and connecting it to
174    /// the platform's interrupt controller.
175    pub fn root_ports(
176        mut self,
177        ports: Vec<GenericPcieRootPortDefinition>,
178        msi_target: &'a MsiTarget,
179    ) -> Self {
180        self.root_ports = Some((ports, msi_target));
181        self
182    }
183
184    /// Set the first PCI device number to assign to root ports (default 0).
185    ///
186    /// Root ports are placed at consecutive device numbers starting from
187    /// this value. Use a non-zero value to reserve lower device numbers
188    /// for RCiEPs (e.g., an IOMMU at device 0).
189    pub fn first_port_device_number(mut self, device: u8) -> Self {
190        self.first_port_device_number = device;
191        self
192    }
193
194    /// Set the CHBCR (Component Register BAR) MMIO range for CXL mode.
195    ///
196    /// When set, CXL component registers are allocated and a CHBCR MMIO
197    /// region is mapped.
198    pub fn chbcr_range(mut self, range: Option<MemoryRange>) -> Self {
199        self.chbcr_range = range;
200        self
201    }
202
203    /// Build the root complex.
204    ///
205    /// Returns an error if the root port count exceeds the available
206    /// device/function slots (32 devices × 8 functions = 256 max).
207    pub fn build(self) -> Result<GenericPcieRootComplex, InvalidRootComplexError> {
208        let Self {
209            register_mmio,
210            bus_range,
211            ecam_range,
212            root_ports,
213            first_port_device_number,
214            chbcr_range,
215        } = self;
216
217        let start_bus = *bus_range.start();
218        let end_bus = *bus_range.end();
219
220        let mut ecam = register_mmio.new_io_region("ecam", ecam_range.len());
221        ecam.map(ecam_range.start());
222
223        // Presence of CHBCR range indicates CXL mode, which needs a component-register
224        // backing object even if no capability payload blocks are registered yet.
225        let cxl_component_registers = chbcr_range.as_ref().map(|_| CxlComponentRegisters::new());
226
227        let chbcr = chbcr_range.map(|range| {
228            tracing::info!(
229                root_bus_start = start_bus,
230                root_bus_end = end_bus,
231                start = range.start(),
232                end = range.end(),
233                len = range.len(),
234                "pcie root complex CHBCR range"
235            );
236            let mut region = register_mmio.new_io_region("chbcr", range.len());
237            region.map(range.start());
238            region
239        });
240
241        let mut devices: Vec<(u8, BusDevice)> = Vec::new();
242
243        if let Some((ports, msi_target)) = root_ports {
244            // Pack root ports into consecutive devfn values, 8 functions
245            // per device slot, mirroring the switch downstream-port pattern.
246            let port_count = ports.len();
247            let max = 32usize.saturating_sub(first_port_device_number as usize) * 8;
248            if port_count > max {
249                return Err(InvalidRootComplexError { port_count, max });
250            }
251
252            let multi_function = port_count > 1;
253
254            for (i, definition) in ports.into_iter().enumerate() {
255                let device = (i / 8) + first_port_device_number as usize;
256                let function = i % 8;
257                let devfn = ((device as u8) << BDF_DEVICE_SHIFT) | function as u8;
258                let hotplug_slot_number = if definition.hotplug {
259                    Some(i as u32 + 1)
260                } else {
261                    None
262                };
263                let port_msi_target = msi_target.with_devfn(devfn);
264                let root_port = RootPort::new(
265                    register_mmio,
266                    definition.name.clone(),
267                    multi_function,
268                    hotplug_slot_number,
269                    &port_msi_target,
270                    definition.settings,
271                );
272                devices.push((
273                    devfn,
274                    BusDevice::RootPort {
275                        name: definition.name,
276                        port: Box::new(root_port),
277                    },
278                ));
279            }
280        }
281
282        Ok(GenericPcieRootComplex {
283            start_bus,
284            end_bus,
285            ecam,
286            chbcr,
287            cxl_component_registers,
288            devices,
289        })
290    }
291}
292
293impl GenericPcieRootComplex {
294    /// Returns a builder for constructing a new `GenericPcieRootComplex`.
295    pub fn builder<'a>(
296        register_mmio: &'a mut dyn RegisterMmioIntercept,
297        bus_range: RangeInclusive<u8>,
298        ecam_range: MemoryRange,
299    ) -> GenericPcieRootComplexBuilder<'a> {
300        assert_eq!(
301            ecam_size_from_bus_numbers(*bus_range.start(), *bus_range.end()),
302            ecam_range.len(),
303            "ECAM range size does not match bus range"
304        );
305
306        GenericPcieRootComplexBuilder {
307            register_mmio,
308            bus_range,
309            ecam_range,
310            root_ports: None,
311            first_port_device_number: 0,
312            chbcr_range: None,
313        }
314    }
315
316    /// Reads CHBCR bytes from the CXL component-register backing object.
317    ///
318    /// `offset` is relative to the start of the CHBCR MMIO range.
319    fn read_chbcr_component_registers(&self, offset: u16, data: &mut [u8]) -> IoResult {
320        let Some(component_regs) = &self.cxl_component_registers else {
321            data.fill(0);
322            return IoResult::Ok;
323        };
324
325        match component_regs.read(offset, data) {
326            IoResult::Err(IoError::InvalidRegister) => {
327                // Treat unmapped CHBCR offsets as reserved MMIO reads.
328                data.fill(0);
329                IoResult::Ok
330            }
331            res => res,
332        }
333    }
334
335    /// Writes CHBCR bytes into the CXL component-register backing object.
336    ///
337    /// `offset` is relative to the start of the CHBCR MMIO range.
338    fn write_chbcr_component_registers(&mut self, offset: u16, data: &[u8]) -> IoResult {
339        let Some(component_regs) = &mut self.cxl_component_registers else {
340            return IoResult::Ok;
341        };
342
343        match component_regs.write(offset, data) {
344            // Treat unmapped CHBCR offsets as handled MMIO writes.
345            IoResult::Err(IoError::InvalidRegister) => IoResult::Ok,
346            res => res,
347        }
348    }
349
350    /// Attach the provided `GenericPciBusDevice` to the port identified by
351    /// its devfn (device << 3 | function).
352    pub fn add_pcie_device(
353        &mut self,
354        port_devfn: u8,
355        name: impl AsRef<str>,
356        dev: Box<dyn GenericPciBusDevice>,
357    ) -> Result<(), Arc<str>> {
358        let root_port = match self.devices.iter_mut().find(|(d, _)| *d == port_devfn) {
359            Some((_, BusDevice::RootPort { port, .. })) => port,
360            Some((_, BusDevice::Rciep { name: existing, .. })) => return Err(existing.clone()),
361            None => return Err(format!("devfn {port_devfn} is not a root port").into()),
362        };
363
364        root_port.connect_device(name, dev)
365    }
366
367    /// Enumerate the downstream ports of the root complex.
368    pub fn downstream_ports(&self) -> Vec<DownstreamPortInfo> {
369        self.devices
370            .iter()
371            .filter_map(|(devfn, d)| match d {
372                BusDevice::RootPort { name, port } => Some(DownstreamPortInfo {
373                    devfn: *devfn,
374                    name: name.clone(),
375                    bus_range: port.port.bus_range(),
376                }),
377                BusDevice::Rciep { .. } => None,
378            })
379            .collect()
380    }
381
382    /// Hot-add a device to a named port.
383    pub fn hotplug_add_device(
384        &mut self,
385        port_name: &str,
386        device_name: &str,
387        device: Box<dyn GenericPciBusDevice>,
388    ) -> anyhow::Result<()> {
389        let root_port = self
390            .devices
391            .iter_mut()
392            .find_map(|(_, d)| match d {
393                BusDevice::RootPort { name, port } if name.as_ref() == port_name => Some(port),
394                BusDevice::RootPort { .. } | BusDevice::Rciep { .. } => None,
395            })
396            .ok_or_else(|| anyhow::anyhow!("port '{}' not found", port_name))?;
397        root_port.port.hotplug_add_device(device_name, device)
398    }
399
400    /// Hot-remove the device from a named port.
401    pub fn hotplug_remove_device(&mut self, port_name: &str) -> anyhow::Result<()> {
402        let root_port = self
403            .devices
404            .iter_mut()
405            .find_map(|(_, d)| match d {
406                BusDevice::RootPort { name, port } if name.as_ref() == port_name => Some(port),
407                BusDevice::RootPort { .. } | BusDevice::Rciep { .. } => None,
408            })
409            .ok_or_else(|| anyhow::anyhow!("port '{}' not found", port_name))?;
410        root_port.port.hotplug_remove_device()
411    }
412
413    /// Attach a Root Complex Integrated Endpoint (RCiEP) at the given
414    /// devfn (device << 3 | function) on the start bus of this root complex.
415    ///
416    /// RCiEPs are Type 0 PCI functions that appear directly on the start
417    /// bus alongside root ports (e.g., an AMD IOMMU at device 0).
418    /// They do not sit behind a downstream port and have a fixed BDF.
419    ///
420    /// Most RCiEPs should be registered at function 0. Config space
421    /// accesses to other functions of the same device will be forwarded
422    /// to the function 0 device via
423    /// [`pci_cfg_read_with_routing`](GenericPciBusDevice::pci_cfg_read_with_routing),
424    /// whose default implementation returns all-1s (no device present).
425    /// A multi-function RCiEP should override `pci_cfg_read_with_routing`
426    /// and `pci_cfg_write_with_routing` to handle non-zero functions.
427    pub fn add_rciep(
428        &mut self,
429        devfn: u8,
430        name: impl Into<Arc<str>>,
431        dev: Box<dyn GenericPciBusDevice>,
432    ) -> Result<(), Arc<str>> {
433        let name = name.into();
434        match self.devices.binary_search_by_key(&devfn, |(d, _)| *d) {
435            Ok(i) => {
436                let existing = match &self.devices[i].1 {
437                    BusDevice::RootPort { name, .. } | BusDevice::Rciep { name, .. } => {
438                        name.clone()
439                    }
440                };
441                return Err(existing);
442            }
443            Err(i) => {
444                self.devices
445                    .insert(i, (devfn, BusDevice::Rciep { name, dev }));
446            }
447        }
448        Ok(())
449    }
450
451    fn decode_ecam_access<'a>(&'a mut self, addr: u64) -> DecodedEcamAccess<'a> {
452        let ecam_offset = match self.ecam.offset_of(addr) {
453            Some(offset) => offset,
454            None => {
455                return DecodedEcamAccess::UnexpectedIntercept;
456            }
457        };
458
459        let ecam_based_bdf = (ecam_offset >> PAGE_SHIFT) as u16;
460        let bus_number = ((ecam_based_bdf >> BDF_BUS_SHIFT) as u8) + self.start_bus;
461        let device_function = (ecam_based_bdf & BDF_DEVICE_FUNCTION_MASK) as u8;
462        let cfg_offset_within_function = (ecam_offset & PAGE_OFFSET_MASK) as u16;
463
464        if bus_number == self.start_bus {
465            let function = device_function & 0x7;
466            // Look up the exact devfn first; if not found, fall back to
467            // function 0 of the same device so that multi-function
468            // endpoints can handle the access via
469            // `pci_cfg_read_with_routing`.
470            let devfn_fn0 = device_function & !7;
471            let mut idx = None;
472            let mut exact = false;
473            for (i, (d, _)) in self.devices.iter().enumerate() {
474                if *d == device_function {
475                    idx = Some(i);
476                    exact = true;
477                    break;
478                }
479                if *d == devfn_fn0 {
480                    idx = Some(i);
481                }
482                if *d > device_function {
483                    break;
484                }
485            }
486            match idx.map(|i| (exact, &mut self.devices[i].1)) {
487                // Exact devfn match for a root port — return its config space.
488                Some((true, BusDevice::RootPort { port, .. })) => {
489                    return DecodedEcamAccess::InternalBus(port, cfg_offset_within_function);
490                }
491                // Fallback (fn0) match for a root port — the target function
492                // is not a root port, so this devfn is unroutable.
493                Some((false, BusDevice::RootPort { .. })) => {
494                    return DecodedEcamAccess::Unroutable;
495                }
496                Some((_, BusDevice::Rciep { dev, .. })) => {
497                    return DecodedEcamAccess::Rciep(
498                        dev.as_mut(),
499                        function,
500                        cfg_offset_within_function,
501                    );
502                }
503                _ => {
504                    return DecodedEcamAccess::Unroutable;
505                }
506            }
507        } else if bus_number > self.start_bus && bus_number <= self.end_bus {
508            for (_, d) in self.devices.iter_mut() {
509                if let BusDevice::RootPort { port, .. } = d {
510                    if port
511                        .port
512                        .cfg_space
513                        .assigned_bus_range()
514                        .contains(&bus_number)
515                    {
516                        return DecodedEcamAccess::DownstreamPort(
517                            port,
518                            bus_number,
519                            device_function,
520                            cfg_offset_within_function,
521                        );
522                    }
523                }
524            }
525            return DecodedEcamAccess::Unroutable;
526        }
527
528        DecodedEcamAccess::UnexpectedIntercept
529    }
530
531    fn mmio_read_non_ecam(&mut self, addr: u64, data: &mut [u8]) -> Option<IoResult> {
532        if let Some(chbcr) = &self.chbcr {
533            if let Some(offset) = chbcr.offset_of(addr) {
534                let Some(offset) = u16::try_from(offset).ok() else {
535                    data.fill(0);
536                    return Some(IoResult::Ok);
537                };
538                return Some(self.read_chbcr_component_registers(offset, data));
539            }
540        }
541
542        for (_, d) in self.devices.iter_mut() {
543            if let BusDevice::RootPort { port, .. } = d {
544                if let Some((bar, offset)) = port.port.find_bar(addr) {
545                    if let Err(err) =
546                        validate_aligned_access(addr, data.len(), &BAR_ALLOWED_ACCESS_SIZES)
547                    {
548                        return Some(IoResult::Err(err));
549                    }
550                    return Some(port.port.bar_mmio_read(bar, offset, data));
551                }
552            }
553        }
554
555        None
556    }
557
558    fn mmio_write_non_ecam(&mut self, addr: u64, data: &[u8]) -> Option<IoResult> {
559        if let Some(chbcr) = &self.chbcr {
560            if let Some(offset) = chbcr.offset_of(addr) {
561                let Some(offset) = u16::try_from(offset).ok() else {
562                    return Some(IoResult::Err(IoError::InvalidRegister));
563                };
564                return Some(self.write_chbcr_component_registers(offset, data));
565            }
566        }
567
568        for (_, d) in self.devices.iter_mut() {
569            if let BusDevice::RootPort { port, .. } = d {
570                if let Some((bar, offset)) = port.port.find_bar(addr) {
571                    if let Err(err) =
572                        validate_aligned_access(addr, data.len(), &BAR_ALLOWED_ACCESS_SIZES)
573                    {
574                        return Some(IoResult::Err(err));
575                    }
576                    return Some(port.port.bar_mmio_write(bar, offset, data));
577                }
578            }
579        }
580
581        None
582    }
583}
584
585fn ecam_size_from_bus_numbers(start_bus: u8, end_bus: u8) -> u64 {
586    assert!(end_bus >= start_bus);
587    let bus_count = (end_bus as u16) - (start_bus as u16) + 1;
588    (bus_count as u64) * (MAX_FUNCTIONS_PER_BUS as u64) * PAGE_SIZE64
589}
590
591impl ChangeDeviceState for GenericPcieRootComplex {
592    fn start(&mut self) {}
593
594    async fn stop(&mut self) {}
595
596    async fn reset(&mut self) {
597        for (_, d) in self.devices.iter_mut() {
598            if let BusDevice::RootPort { port, .. } = d {
599                port.port.cfg_space.reset();
600            }
601        }
602    }
603}
604
605impl ChipsetDevice for GenericPcieRootComplex {
606    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
607        Some(self)
608    }
609}
610
611const ECAM_ALLOWED_ACCESS_SIZES: [usize; 3] = [1, 2, 4];
612const BAR_ALLOWED_ACCESS_SIZES: [usize; 4] = [1, 2, 4, 8];
613
614fn validate_aligned_access(
615    address: u64,
616    len: usize,
617    allowed_sizes: &[usize],
618) -> Result<(), IoError> {
619    if !allowed_sizes.contains(&len) {
620        return Err(IoError::InvalidAccessSize);
621    }
622
623    if !address.is_multiple_of(len as u64) {
624        return Err(IoError::UnalignedAccess);
625    }
626
627    Ok(())
628}
629
630macro_rules! check_result {
631    ($result:expr) => {
632        match $result {
633            IoResult::Ok => (),
634            res => {
635                return res;
636            }
637        }
638    };
639}
640
641impl MmioIntercept for GenericPcieRootComplex {
642    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
643        if let Some(result) = self.mmio_read_non_ecam(addr, data) {
644            return result;
645        }
646
647        if let Err(err) = validate_aligned_access(addr, data.len(), &ECAM_ALLOWED_ACCESS_SIZES) {
648            return IoResult::Err(err);
649        }
650
651        // N.B. Emulators internally only support 4-byte aligned accesses to
652        // 4-byte registers, but the guest can use 1-, 2-, or 4 byte memory
653        // instructions to access ECAM. This function reads the 4-byte aligned
654        // value then shifts it around as needed before copying the data into
655        // the intercept completion bytes.
656
657        let dword_aligned_addr = addr & !3;
658        let mut dword_value = !0;
659        let start_bus = self.start_bus;
660        match self.decode_ecam_access(dword_aligned_addr) {
661            DecodedEcamAccess::UnexpectedIntercept => {
662                tracelimit::warn_ratelimited!(addr, "unexpected intercept at ECAM address");
663            }
664            DecodedEcamAccess::Unroutable => {
665                tracing::debug!(addr, "unroutable config space access");
666            }
667            DecodedEcamAccess::InternalBus(port, cfg_offset) => {
668                check_result!(port.port.cfg_space.read_u32(cfg_offset, &mut dword_value));
669            }
670            DecodedEcamAccess::Rciep(dev, function, cfg_offset) => {
671                let bus = start_bus;
672                if let Some(result) =
673                    dev.pci_cfg_read_with_routing(bus, bus, function, cfg_offset, &mut dword_value)
674                {
675                    check_result!(result);
676                }
677            }
678            DecodedEcamAccess::DownstreamPort(port, bus_number, function, cfg_offset) => {
679                check_result!(port.forward_cfg_read(
680                    &bus_number,
681                    &function,
682                    cfg_offset & !3,
683                    &mut dword_value,
684                ));
685            }
686        }
687
688        let byte_offset_within_dword = (addr & 3) as usize;
689        data.copy_from_slice(
690            &dword_value.as_bytes()
691                [byte_offset_within_dword..byte_offset_within_dword + data.len()],
692        );
693
694        IoResult::Ok
695    }
696
697    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
698        if let Some(result) = self.mmio_write_non_ecam(addr, data) {
699            return result;
700        }
701
702        if let Err(err) = validate_aligned_access(addr, data.len(), &ECAM_ALLOWED_ACCESS_SIZES) {
703            return IoResult::Err(err);
704        }
705
706        // N.B. Emulators internally only support 4-byte aligned accesses to
707        // 4-byte registers, but the guest can use 1-, 2-, or 4-byte memory
708        // instructions to access ECAM. If the guest is using a 1- or 2-byte
709        // instruction, this function reads the 4-byte aligned configuration
710        // register, masks in the new bytes being written by the guest, and
711        // uses the resulting value for write emulation.
712
713        let dword_aligned_addr = addr & !3;
714        let write_dword = match data.len() {
715            4 => {
716                let mut temp: u32 = 0;
717                temp.as_mut_bytes().copy_from_slice(data);
718                temp
719            }
720            _ => {
721                let mut temp_bytes: [u8; 4] = [0, 0, 0, 0];
722                check_result!(self.mmio_read(dword_aligned_addr, &mut temp_bytes));
723
724                let byte_offset_within_dword = (addr & 3) as usize;
725                temp_bytes[byte_offset_within_dword..byte_offset_within_dword + data.len()]
726                    .copy_from_slice(data);
727
728                let mut temp: u32 = 0;
729                temp.as_mut_bytes().copy_from_slice(&temp_bytes);
730                temp
731            }
732        };
733
734        let start_bus = self.start_bus;
735        match self.decode_ecam_access(dword_aligned_addr) {
736            DecodedEcamAccess::UnexpectedIntercept => {
737                tracelimit::warn_ratelimited!(addr, "unexpected intercept at ECAM address");
738            }
739            DecodedEcamAccess::Unroutable => {
740                tracing::debug!(addr, "unroutable config space access");
741            }
742            DecodedEcamAccess::InternalBus(port, cfg_offset) => {
743                check_result!(port.port.cfg_space.write_u32(cfg_offset, write_dword));
744            }
745            DecodedEcamAccess::Rciep(dev, function, cfg_offset) => {
746                let bus = start_bus;
747                if let Some(result) =
748                    dev.pci_cfg_write_with_routing(bus, bus, function, cfg_offset, write_dword)
749                {
750                    check_result!(result);
751                }
752            }
753            DecodedEcamAccess::DownstreamPort(port, bus_number, function, cfg_offset) => {
754                check_result!(port.forward_cfg_write(
755                    &bus_number,
756                    &function,
757                    cfg_offset,
758                    write_dword,
759                ));
760            }
761        }
762
763        IoResult::Ok
764    }
765}
766
767#[derive(Inspect)]
768struct RootPort {
769    /// The common PCIe port implementation.
770    #[inspect(flatten)]
771    port: PcieDownstreamPort,
772}
773
774impl RootPort {
775    /// Constructs a new [`RootPort`] emulator.
776    ///
777    /// # Arguments
778    /// * `name` - The name for this root port
779    /// * `hotplug_slot_number` - The slot number for hotplug support. `Some(slot_number)` enables hotplug, `None` disables it
780    /// * `msi_target` - MSI target for interrupt delivery
781    /// * `settings` - Express-level port settings (ACS, etc.)
782    pub fn new(
783        register_mmio: &mut dyn RegisterMmioIntercept,
784        name: impl Into<Arc<str>>,
785        multi_function: bool,
786        hotplug_slot_number: Option<u32>,
787        msi_target: &MsiTarget,
788        settings: PciePortSettings,
789    ) -> Self {
790        let name_str = name.into();
791
792        let hardware_ids = HardwareIds {
793            vendor_id: VENDOR_ID,
794            device_id: ROOT_PORT_DEVICE_ID,
795            revision_id: 0,
796            prog_if: ProgrammingInterface::NONE,
797            sub_class: Subclass::BRIDGE_PCI_TO_PCI,
798            base_class: ClassCode::BRIDGE,
799            type0_sub_vendor_id: 0,
800            type0_sub_system_id: 0,
801        };
802
803        let port = PcieDownstreamPort::new(
804            name_str.to_string(),
805            hardware_ids,
806            DevicePortType::RootPort,
807            multi_function,
808            hotplug_slot_number,
809            msi_target,
810            settings,
811            Some(register_mmio),
812            None,
813        );
814
815        Self { port }
816    }
817
818    /// Try to connect a PCIe device, returning an existing device name if the
819    /// port is already occupied.
820    fn connect_device(
821        &mut self,
822        name: impl AsRef<str>,
823        dev: Box<dyn GenericPciBusDevice>,
824    ) -> Result<(), Arc<str>> {
825        let device_name = name.as_ref();
826        let port_name = self.port.name.clone();
827
828        match self.port.add_pcie_device(&port_name, device_name, dev) {
829            Ok(()) => Ok(()),
830            Err(_error) => {
831                // If the connection failed, it means the port is already occupied
832                // We need to get the name of the existing device
833                if let Some((existing_name, _)) = &self.port.link {
834                    tracing::warn!(
835                        "RootPort: '{}' failed to connect device '{}', port already occupied by '{}'",
836                        port_name,
837                        device_name,
838                        existing_name
839                    );
840                    Err(existing_name.clone())
841                } else {
842                    // This shouldn't happen if add_pcie_device works correctly
843                    tracing::error!(
844                        "RootPort: '{}' connection failed for device '{}' but no existing device found",
845                        port_name,
846                        device_name
847                    );
848                    panic!("Port connection failed but no existing device found")
849                }
850            }
851        }
852    }
853
854    fn forward_cfg_read(
855        &mut self,
856        bus: &u8,
857        function: &u8,
858        cfg_offset: u16,
859        value: &mut u32,
860    ) -> IoResult {
861        self.port
862            .forward_cfg_read_with_routing(bus, function, cfg_offset, value)
863    }
864
865    fn forward_cfg_write(
866        &mut self,
867        bus: &u8,
868        function: &u8,
869        cfg_offset: u16,
870        value: u32,
871    ) -> IoResult {
872        self.port
873            .forward_cfg_write_with_routing(bus, function, cfg_offset, value)
874    }
875}
876
877mod save_restore {
878    use super::*;
879    use pci_core::cfg_space_emu::ConfigSpaceType1Emulator;
880    use std::collections::HashSet;
881    use vmcore::save_restore::RestoreError;
882    use vmcore::save_restore::SaveError;
883    use vmcore::save_restore::SaveRestore;
884
885    mod state {
886        use super::ConfigSpaceType1Emulator;
887        use super::CxlComponentRegisters;
888        use super::SaveRestore;
889        use mesh::payload::Protobuf;
890        use vmcore::save_restore::SavedStateRoot;
891
892        type RootPortCfgSpaceSavedState = <ConfigSpaceType1Emulator as SaveRestore>::SavedState;
893        type CxlComponentRegistersSavedState = <CxlComponentRegisters as SaveRestore>::SavedState;
894
895        /// Saved state for a single root port.
896        #[derive(Protobuf)]
897        #[mesh(package = "pcie.root")]
898        pub struct PortSavedState {
899            /// The devfn (device << 3 | function) of this port.
900            #[mesh(1)]
901            pub devfn: u8,
902            /// The root port Type 1 configuration space state.
903            #[mesh(2)]
904            pub cfg_space: RootPortCfgSpaceSavedState,
905            /// Optional CXL component-register state for this port.
906            #[mesh(3)]
907            pub cxl_component_registers: Option<CxlComponentRegistersSavedState>,
908        }
909
910        /// Saved state for the GenericPcieRootComplex.
911        #[derive(Protobuf, SavedStateRoot)]
912        #[mesh(package = "pcie.root")]
913        pub struct SavedState {
914            /// The lowest valid bus number under the root complex.
915            #[mesh(1)]
916            pub start_bus: u8,
917            /// The highest valid bus number under the root complex.
918            #[mesh(2)]
919            pub end_bus: u8,
920            /// Saved state for each root port.
921            #[mesh(3)]
922            pub ports: Vec<PortSavedState>,
923            /// Optional CXL component-register state for CHBCR-backed root complexes.
924            #[mesh(4)]
925            pub cxl_component_registers: Option<CxlComponentRegistersSavedState>,
926        }
927    }
928
929    impl SaveRestore for GenericPcieRootComplex {
930        type SavedState = state::SavedState;
931
932        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
933            // Save all root ports in devfn order.
934            let mut ports = Vec::new();
935            for (devfn, d) in self.devices.iter_mut() {
936                if let BusDevice::RootPort { port, .. } = d {
937                    ports.push(state::PortSavedState {
938                        devfn: *devfn,
939                        cfg_space: port.port.cfg_space.save()?,
940                        cxl_component_registers: port.port.save_cxl_component_registers_state()?,
941                    });
942                }
943            }
944
945            Ok(state::SavedState {
946                start_bus: self.start_bus,
947                end_bus: self.end_bus,
948                ports,
949                cxl_component_registers: self
950                    .cxl_component_registers
951                    .as_mut()
952                    .map(|regs| regs.save())
953                    .transpose()?,
954            })
955        }
956
957        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
958            let state::SavedState {
959                start_bus,
960                end_bus,
961                ports,
962                cxl_component_registers,
963            } = state;
964
965            // Validate that bus numbers match
966            if start_bus != self.start_bus || end_bus != self.end_bus {
967                return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
968                    "bus number mismatch: saved ({}-{}), current ({}-{})",
969                    start_bus,
970                    end_bus,
971                    self.start_bus,
972                    self.end_bus
973                )));
974            }
975
976            // Validate port count matches
977            let root_port_count = self
978                .devices
979                .iter()
980                .filter(|(_, d)| matches!(d, BusDevice::RootPort { .. }))
981                .count();
982            if ports.len() != root_port_count {
983                return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
984                    "root port count mismatch: saved {}, current {}",
985                    ports.len(),
986                    root_port_count
987                )));
988            }
989
990            let mut seen_ports = HashSet::with_capacity(ports.len());
991
992            // Restore each saved port by devfn.
993            for port_state in ports {
994                if !seen_ports.insert(port_state.devfn) {
995                    return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
996                        "duplicate root port devfn {} in saved state",
997                        port_state.devfn
998                    )));
999                }
1000
1001                match self
1002                    .devices
1003                    .iter_mut()
1004                    .find(|(d, _)| *d == port_state.devfn)
1005                {
1006                    Some((_, BusDevice::RootPort { port, .. })) => {
1007                        port.port.cfg_space.restore(port_state.cfg_space)?;
1008                        port.port.restore_cxl_component_registers_state(
1009                            port_state.cxl_component_registers,
1010                        )?;
1011                    }
1012                    Some((_, BusDevice::Rciep { .. })) | None => {
1013                        return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
1014                            "root port devfn {} not found",
1015                            port_state.devfn
1016                        )));
1017                    }
1018                }
1019            }
1020
1021            match (&mut self.cxl_component_registers, cxl_component_registers) {
1022                (Some(current), Some(saved)) => current.restore(saved)?,
1023                (None, None) => {}
1024                (Some(_), None) => {
1025                    return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
1026                        "CXL mode mismatch: current root complex has CHBCR registers but saved state does not"
1027                    )));
1028                }
1029                (None, Some(_)) => {
1030                    return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
1031                        "CXL mode mismatch: saved state has CHBCR registers but current root complex does not"
1032                    )));
1033                }
1034            }
1035
1036            Ok(())
1037        }
1038    }
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043    use super::*;
1044    use crate::test_helpers::*;
1045    use cxl_spec::CxlComponentRegisterType;
1046    use cxl_spec::component_registers::test_helper::TestCxlComponentRegisterBlock;
1047    use pal_async::async_test;
1048
1049    fn instantiate_root_complex(
1050        start_bus: u8,
1051        end_bus: u8,
1052        port_count: u16,
1053    ) -> GenericPcieRootComplex {
1054        let port_defs = (0..port_count)
1055            .map(|i| GenericPcieRootPortDefinition {
1056                name: format!("test-port-{}", i).into(),
1057                hotplug: false,
1058                settings: PciePortSettings::default(),
1059            })
1060            .collect();
1061
1062        let mut register_mmio = TestPcieMmioRegistration {};
1063        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(start_bus, end_bus));
1064        let rc_bus_range = AssignedBusRange::new();
1065        rc_bus_range.set_bus_range(start_bus, end_bus);
1066        let msi_conn = pci_core::msi::MsiConnection::new(rc_bus_range, 0);
1067        GenericPcieRootComplex::builder(&mut register_mmio, start_bus..=end_bus, ecam)
1068            .root_ports(port_defs, msi_conn.target())
1069            .build()
1070            .unwrap()
1071    }
1072
1073    fn instantiate_root_complex_with_chbcr(
1074        start_bus: u8,
1075        end_bus: u8,
1076        port_count: u16,
1077        chbcr_start: u64,
1078    ) -> GenericPcieRootComplex {
1079        let port_defs = (0..port_count)
1080            .map(|i| GenericPcieRootPortDefinition {
1081                name: format!("test-port-{}", i).into(),
1082                hotplug: false,
1083                settings: PciePortSettings::default(),
1084            })
1085            .collect();
1086
1087        let mut register_mmio = TestPcieMmioRegistration {};
1088        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(start_bus, end_bus));
1089        let chbcr = MemoryRange::new(
1090            chbcr_start..(chbcr_start + cxl_spec::spec::CXL_COMPONENT_REGISTERS_SIZE_BYTES),
1091        );
1092        let rc_bus_range = AssignedBusRange::new();
1093        rc_bus_range.set_bus_range(start_bus, end_bus);
1094        let msi_conn = pci_core::msi::MsiConnection::new(rc_bus_range, 0);
1095
1096        GenericPcieRootComplex::builder(&mut register_mmio, start_bus..=end_bus, ecam)
1097            .root_ports(port_defs, msi_conn.target())
1098            .chbcr_range(Some(chbcr))
1099            .build()
1100            .unwrap()
1101    }
1102
1103    #[test]
1104    fn test_create() {
1105        assert_eq!(
1106            instantiate_root_complex(0, 0, 1).downstream_ports().len(),
1107            1
1108        );
1109        assert_eq!(
1110            instantiate_root_complex(0, 1, 1).downstream_ports().len(),
1111            1
1112        );
1113        assert_eq!(
1114            instantiate_root_complex(1, 1, 1).downstream_ports().len(),
1115            1
1116        );
1117        assert_eq!(
1118            instantiate_root_complex(255, 255, 1)
1119                .downstream_ports()
1120                .len(),
1121            1
1122        );
1123
1124        assert_eq!(
1125            instantiate_root_complex(0, 0, 4).downstream_ports().len(),
1126            4
1127        );
1128
1129        assert_eq!(
1130            instantiate_root_complex(0, 255, 32)
1131                .downstream_ports()
1132                .len(),
1133            32
1134        );
1135        assert_eq!(
1136            instantiate_root_complex(32, 32, 32)
1137                .downstream_ports()
1138                .len(),
1139            32
1140        );
1141        assert_eq!(
1142            instantiate_root_complex(255, 255, 32)
1143                .downstream_ports()
1144                .len(),
1145            32
1146        );
1147
1148        // Multi-function packing allows more than 32 ports.
1149        assert_eq!(
1150            instantiate_root_complex(0, 255, 64)
1151                .downstream_ports()
1152                .len(),
1153            64
1154        );
1155        assert_eq!(
1156            instantiate_root_complex(0, 255, 256)
1157                .downstream_ports()
1158                .len(),
1159            256
1160        );
1161    }
1162
1163    #[test]
1164    fn test_ecam_size() {
1165        // Single bus
1166        assert_eq!(ecam_size_from_bus_numbers(0, 0), 0x10_0000);
1167        assert_eq!(ecam_size_from_bus_numbers(32, 32), 0x10_0000);
1168        assert_eq!(ecam_size_from_bus_numbers(255, 255), 0x10_0000);
1169
1170        // Two bus
1171        assert_eq!(ecam_size_from_bus_numbers(0, 1), 0x20_0000);
1172        assert_eq!(ecam_size_from_bus_numbers(32, 33), 0x20_0000);
1173        assert_eq!(ecam_size_from_bus_numbers(254, 255), 0x20_0000);
1174
1175        // Everything
1176        assert_eq!(ecam_size_from_bus_numbers(0, 255), 0x1000_0000);
1177    }
1178
1179    #[test]
1180    fn test_probe_ports_via_config_space() {
1181        let mut rc = instantiate_root_complex(0, 255, 4);
1182        // With multi-function packing, 4 ports are at devfn 0..3
1183        // (device 0, functions 0..3). ECAM offset = devfn * 4096.
1184        for devfn in 0..4u64 {
1185            let mut vendor_device: u32 = 0;
1186            rc.mmio_read(devfn * 4096, vendor_device.as_mut_bytes())
1187                .unwrap();
1188            assert_eq!(vendor_device, 0xC030_1414);
1189
1190            let mut value_16: u16 = 0;
1191            rc.mmio_read(devfn * 4096, value_16.as_mut_bytes()).unwrap();
1192            assert_eq!(value_16, 0x1414);
1193
1194            rc.mmio_read(devfn * 4096 + 2, value_16.as_mut_bytes())
1195                .unwrap();
1196            assert_eq!(value_16, 0xC030);
1197        }
1198
1199        for devfn in 4..10u64 {
1200            let mut value_32: u32 = 0;
1201            rc.mmio_read(devfn * 4096, value_32.as_mut_bytes()).unwrap();
1202            assert_eq!(value_32, 0xFFFF_FFFF);
1203
1204            let mut value_16: u16 = 0;
1205            rc.mmio_read(devfn * 4096, value_16.as_mut_bytes()).unwrap();
1206            assert_eq!(value_16, 0xFFFF);
1207            rc.mmio_read(devfn * 4096 + 2, value_16.as_mut_bytes())
1208                .unwrap();
1209            assert_eq!(value_16, 0xFFFF);
1210        }
1211    }
1212
1213    #[test]
1214    fn test_add_downstream_device_to_port() {
1215        let mut rc = instantiate_root_complex(0, 0, 1);
1216
1217        let endpoint1 = TestPcieEndpoint::new(
1218            |offset, value| match offset {
1219                0x0 => {
1220                    *value = 0xAAAA_AAAA;
1221                    Some(IoResult::Ok)
1222                }
1223                _ => Some(IoResult::Err(IoError::InvalidRegister)),
1224            },
1225            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1226        );
1227
1228        let endpoint2 = TestPcieEndpoint::new(
1229            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1230            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1231        );
1232
1233        rc.add_pcie_device(0, "ep1", Box::new(endpoint1)).unwrap();
1234
1235        rc.add_pcie_device(0, "ep2", Box::new(endpoint2))
1236            .expect_err("should fail: port already occupied");
1237    }
1238
1239    #[test]
1240    fn test_root_port_cfg_forwarding() {
1241        const SECONDARY_BUS_NUM_REG: u64 = 0x19;
1242        const SUBOORDINATE_BUS_NUM_REG: u64 = 0x1A;
1243
1244        let mut rc = instantiate_root_complex(0, 255, 1);
1245
1246        // Pre-bus number assignment, random accesses return 1s.
1247        let mut value_32: u32 = 0;
1248        rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
1249        assert_eq!(value_32, 0xFFFF_FFFF);
1250
1251        // Secondary and suboordinate bus number registers are both
1252        // read / write, defaulting to 0.
1253        let mut bus_number: u8 = 0xFF;
1254        rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
1255            .unwrap();
1256        assert_eq!(bus_number, 0);
1257        rc.mmio_read(SUBOORDINATE_BUS_NUM_REG, bus_number.as_mut_bytes())
1258            .unwrap();
1259        assert_eq!(bus_number, 0);
1260
1261        rc.mmio_write(SECONDARY_BUS_NUM_REG, &[1]).unwrap();
1262        rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
1263            .unwrap();
1264        assert_eq!(bus_number, 1);
1265
1266        rc.mmio_write(SUBOORDINATE_BUS_NUM_REG, &[2]).unwrap();
1267        rc.mmio_read(SUBOORDINATE_BUS_NUM_REG, bus_number.as_mut_bytes())
1268            .unwrap();
1269        assert_eq!(bus_number, 2);
1270
1271        // Bus numbers assigned, but no endpoint attached yet.
1272        rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
1273        assert_eq!(value_32, 0xFFFF_FFFF);
1274
1275        let endpoint = TestPcieEndpoint::new(
1276            |offset, value| match offset {
1277                0x0 => {
1278                    *value = 0xDEAD_BEEF;
1279                    Some(IoResult::Ok)
1280                }
1281                _ => Some(IoResult::Err(IoError::InvalidRegister)),
1282            },
1283            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1284        );
1285
1286        rc.add_pcie_device(0, "test-ep", Box::new(endpoint))
1287            .unwrap();
1288
1289        // The secondary bus behind root port 0 has been assigned bus number
1290        // 1, so now the attached endpoint is accessible.
1291        rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
1292        assert_eq!(value_32, 0xDEAD_BEEF);
1293
1294        // Reassign the secondary bus number to 2.
1295        rc.mmio_write(SECONDARY_BUS_NUM_REG, &[2]).unwrap();
1296        rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
1297            .unwrap();
1298        assert_eq!(bus_number, 2);
1299
1300        // The endpoint is no longer accessible at bus number 1, and is now
1301        // accessible at bus number 2.
1302        rc.mmio_read(256 * 4096, value_32.as_mut_bytes()).unwrap();
1303        assert_eq!(value_32, 0xFFFF_FFFF);
1304        rc.mmio_read(2 * 256 * 4096, value_32.as_mut_bytes())
1305            .unwrap();
1306        assert_eq!(value_32, 0xDEAD_BEEF);
1307    }
1308
1309    #[test]
1310    fn test_chbcr_reads_cxl_component_header_in_cxl_mode() {
1311        let chbcr_start = 0x2000_0000;
1312        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1313
1314        // CHBCR + 0x1000 maps to cache/mem page 0 header dword.
1315        let mut header: u32 = 0;
1316        rc.mmio_read(chbcr_start + 0x1000, header.as_mut_bytes())
1317            .unwrap();
1318        assert_eq!(header, 0x0011_0001);
1319    }
1320
1321    #[test]
1322    fn test_chbcr_read_write_redirects_to_component_registers() {
1323        let chbcr_start = 0x3000_0000;
1324        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1325
1326        // Install one payload register block into the component-register space.
1327        assert!(
1328            rc.cxl_component_registers
1329                .as_mut()
1330                .expect("CXL mode must allocate component registers")
1331                .add_register(Box::new(TestCxlComponentRegisterBlock::new(
1332                    CxlComponentRegisterType::CXL_CACHE_MEM_REGISTER,
1333                    16,
1334                )))
1335        );
1336
1337        let value = 0x1122_3344u32;
1338        rc.mmio_write(chbcr_start + 0x1008, value.as_bytes())
1339            .unwrap();
1340
1341        let mut read_back: u32 = 0;
1342        rc.mmio_read(chbcr_start + 0x1008, read_back.as_mut_bytes())
1343            .unwrap();
1344        assert_eq!(read_back, value);
1345    }
1346
1347    #[test]
1348    fn test_chbcr_8byte_read_write_redirects_to_component_registers() {
1349        let chbcr_start = 0x3001_0000;
1350        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1351
1352        // Install one payload register block into the component-register space.
1353        assert!(
1354            rc.cxl_component_registers
1355                .as_mut()
1356                .expect("CXL mode must allocate component registers")
1357                .add_register(Box::new(TestCxlComponentRegisterBlock::new(
1358                    CxlComponentRegisterType::CXL_CACHE_MEM_REGISTER,
1359                    16,
1360                )))
1361        );
1362
1363        let value = 0x1122_3344_5566_7788u64;
1364        rc.mmio_write(chbcr_start + 0x1008, value.as_bytes())
1365            .unwrap();
1366
1367        let mut read_back: u64 = 0;
1368        rc.mmio_read(chbcr_start + 0x1008, read_back.as_mut_bytes())
1369            .unwrap();
1370        assert_eq!(read_back, value);
1371    }
1372
1373    #[test]
1374    fn test_chbcr_unmapped_read_is_handled() {
1375        let chbcr_start = 0x3002_0000;
1376        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1377
1378        // This offset is not synthesized/populated and should still be
1379        // treated as a handled CHBCR MMIO read.
1380        let mut value: u64 = 0;
1381        rc.mmio_read(chbcr_start + 0x800, value.as_mut_bytes())
1382            .unwrap();
1383        assert_eq!(value, 0);
1384    }
1385
1386    #[test]
1387    fn test_chbcr_unmapped_write_is_handled() {
1388        let chbcr_start = 0x3003_0000;
1389        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1390
1391        // Unmapped CHBCR writes should be ignored but treated as handled.
1392        let value = 0x0123_4567_89ab_cdefu64;
1393        rc.mmio_write(chbcr_start + 0x800, value.as_bytes())
1394            .unwrap();
1395    }
1396
1397    #[async_test]
1398    async fn test_reset() {
1399        const COMMAND_REG: u64 = 0x4;
1400        const COMMAND_REG_VALUE: u16 = 0x0004;
1401        const PORT0_ECAM: u64 = 0;
1402        const PORT1_ECAM: u64 = 4096;
1403
1404        let mut rc = instantiate_root_complex(0, 255, 2);
1405        let mut value_16: u16 = 0;
1406
1407        // Write the command register of both ports with a reasonable value.
1408        rc.mmio_write(PORT0_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
1409            .unwrap();
1410        rc.mmio_write(PORT1_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
1411            .unwrap();
1412        rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1413            .unwrap();
1414        assert_eq!(value_16, COMMAND_REG_VALUE);
1415        rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1416            .unwrap();
1417        assert_eq!(value_16, COMMAND_REG_VALUE);
1418
1419        // Reset the emulator, and ensure programming was cleared.
1420        rc.reset().await;
1421        rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1422            .unwrap();
1423        assert_eq!(value_16, 0);
1424        rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1425            .unwrap();
1426        assert_eq!(value_16, 0);
1427
1428        // Re-write the command register of both ports after reset.
1429        rc.mmio_write(PORT0_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
1430            .unwrap();
1431        rc.mmio_write(PORT1_ECAM + COMMAND_REG, COMMAND_REG_VALUE.as_bytes())
1432            .unwrap();
1433        rc.mmio_read(PORT0_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1434            .unwrap();
1435        assert_eq!(value_16, COMMAND_REG_VALUE);
1436        rc.mmio_read(PORT1_ECAM + COMMAND_REG, value_16.as_mut_bytes())
1437            .unwrap();
1438        assert_eq!(value_16, COMMAND_REG_VALUE);
1439    }
1440
1441    #[test]
1442    fn test_root_port_hotplug_options() {
1443        // Test with hotplug disabled (None)
1444        let root_port_no_hotplug = {
1445            let mut register_mmio = TestPcieMmioRegistration {};
1446            let c = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1447            RootPort::new(
1448                &mut register_mmio,
1449                "test-port-no-hotplug",
1450                false,
1451                None,
1452                c.target(),
1453                PciePortSettings::default(),
1454            )
1455        };
1456        // We can't easily verify hotplug is disabled without accessing internal state,
1457        // but we can verify the port was created successfully
1458        let mut vendor_device_id: u32 = 0;
1459        root_port_no_hotplug
1460            .port
1461            .cfg_space
1462            .read_u32(0x0, &mut vendor_device_id)
1463            .unwrap();
1464        let expected = (ROOT_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
1465        assert_eq!(vendor_device_id, expected);
1466
1467        // Test with hotplug enabled (Some(slot_number))
1468        let root_port_with_hotplug = {
1469            let mut register_mmio = TestPcieMmioRegistration {};
1470            let c = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1471            RootPort::new(
1472                &mut register_mmio,
1473                "test-port-hotplug",
1474                false,
1475                Some(5),
1476                c.target(),
1477                PciePortSettings::default(),
1478            )
1479        };
1480        let mut vendor_device_id_hotplug: u32 = 0;
1481        root_port_with_hotplug
1482            .port
1483            .cfg_space
1484            .read_u32(0x0, &mut vendor_device_id_hotplug)
1485            .unwrap();
1486        assert_eq!(vendor_device_id_hotplug, expected);
1487        // The slot number and hotplug capability would be tested via PCIe capability registers
1488        // but that requires more complex setup
1489    }
1490
1491    #[test]
1492    fn test_root_port_invalid_bus_range_handling() {
1493        let mut root_port = {
1494            let mut register_mmio = TestPcieMmioRegistration {};
1495            let c = pci_core::msi::MsiConnection::new(AssignedBusRange::new(), 0);
1496            RootPort::new(
1497                &mut register_mmio,
1498                "test-port",
1499                false,
1500                None,
1501                c.target(),
1502                PciePortSettings::default(),
1503            )
1504        };
1505
1506        // Don't configure bus numbers, so the range should be 0..=0 (invalid)
1507        let bus_range = root_port.port.cfg_space.assigned_bus_range();
1508        assert_eq!(bus_range, 0..=0);
1509
1510        // Test that forwarding returns Ok but doesn't crash when bus range is invalid
1511        let mut value = 0u32;
1512        let result = root_port
1513            .port
1514            .forward_cfg_read_with_routing(&1, &0, 0x0, &mut value);
1515        assert!(matches!(result, IoResult::Ok));
1516
1517        let result = root_port
1518            .port
1519            .forward_cfg_write_with_routing(&1, &0, 0x0, value);
1520        assert!(matches!(result, IoResult::Ok));
1521    }
1522
1523    #[test]
1524    fn test_save_restore_basic() {
1525        use vmcore::save_restore::SaveRestore;
1526
1527        let mut rc = instantiate_root_complex(0, 255, 4);
1528
1529        // Save the initial state
1530        let saved_state = rc.save().expect("save should succeed");
1531
1532        // Verify the saved state has the correct values
1533        assert_eq!(saved_state.start_bus, 0);
1534        assert_eq!(saved_state.end_bus, 255);
1535        assert_eq!(saved_state.ports.len(), 4);
1536
1537        // Restore the state to a new root complex
1538        let mut rc2 = instantiate_root_complex(0, 255, 4);
1539        rc2.restore(saved_state).expect("restore should succeed");
1540    }
1541
1542    #[test]
1543    fn test_save_restore_with_bus_configuration() {
1544        use vmcore::save_restore::SaveRestore;
1545        use zerocopy::IntoBytes;
1546
1547        const SECONDARY_BUS_NUM_REG: u64 = 0x19;
1548        const SUBORDINATE_BUS_NUM_REG: u64 = 0x1A;
1549
1550        let mut rc = instantiate_root_complex(0, 255, 2);
1551
1552        // Configure bus numbers on port 0
1553        rc.mmio_write(SECONDARY_BUS_NUM_REG, &[1]).unwrap();
1554        rc.mmio_write(SUBORDINATE_BUS_NUM_REG, &[10]).unwrap();
1555
1556        // Configure bus numbers on port 1 (at devfn 1 with multi-function packing)
1557        const PORT1_ECAM: u64 = 4096;
1558        rc.mmio_write(PORT1_ECAM + SECONDARY_BUS_NUM_REG, &[11])
1559            .unwrap();
1560        rc.mmio_write(PORT1_ECAM + SUBORDINATE_BUS_NUM_REG, &[20])
1561            .unwrap();
1562
1563        // Verify the bus numbers are set
1564        let mut bus_number: u8 = 0;
1565        rc.mmio_read(SECONDARY_BUS_NUM_REG, bus_number.as_mut_bytes())
1566            .unwrap();
1567        assert_eq!(bus_number, 1);
1568        rc.mmio_read(SUBORDINATE_BUS_NUM_REG, bus_number.as_mut_bytes())
1569            .unwrap();
1570        assert_eq!(bus_number, 10);
1571
1572        rc.mmio_read(
1573            PORT1_ECAM + SECONDARY_BUS_NUM_REG,
1574            bus_number.as_mut_bytes(),
1575        )
1576        .unwrap();
1577        assert_eq!(bus_number, 11);
1578        rc.mmio_read(
1579            PORT1_ECAM + SUBORDINATE_BUS_NUM_REG,
1580            bus_number.as_mut_bytes(),
1581        )
1582        .unwrap();
1583        assert_eq!(bus_number, 20);
1584
1585        // Save the state
1586        let saved_state = rc.save().expect("save should succeed");
1587
1588        // Create a new root complex and restore the state
1589        let mut rc2 = instantiate_root_complex(0, 255, 2);
1590
1591        // Verify the new root complex has default bus numbers before restore
1592        let mut default_bus: u8 = 0xFF;
1593        rc2.mmio_read(SECONDARY_BUS_NUM_REG, default_bus.as_mut_bytes())
1594            .unwrap();
1595        assert_eq!(default_bus, 0);
1596
1597        // Restore the state
1598        rc2.restore(saved_state).expect("restore should succeed");
1599
1600        // Verify the bus numbers are restored
1601        let mut restored_bus: u8 = 0;
1602        rc2.mmio_read(SECONDARY_BUS_NUM_REG, restored_bus.as_mut_bytes())
1603            .unwrap();
1604        assert_eq!(restored_bus, 1);
1605        rc2.mmio_read(SUBORDINATE_BUS_NUM_REG, restored_bus.as_mut_bytes())
1606            .unwrap();
1607        assert_eq!(restored_bus, 10);
1608
1609        rc2.mmio_read(
1610            PORT1_ECAM + SECONDARY_BUS_NUM_REG,
1611            restored_bus.as_mut_bytes(),
1612        )
1613        .unwrap();
1614        assert_eq!(restored_bus, 11);
1615        rc2.mmio_read(
1616            PORT1_ECAM + SUBORDINATE_BUS_NUM_REG,
1617            restored_bus.as_mut_bytes(),
1618        )
1619        .unwrap();
1620        assert_eq!(restored_bus, 20);
1621    }
1622
1623    #[test]
1624    fn test_save_restore_bus_number_mismatch_error() {
1625        use vmcore::save_restore::SaveRestore;
1626
1627        // Create a root complex with specific bus range
1628        let mut rc = instantiate_root_complex(0, 255, 2);
1629
1630        // Save the state
1631        let saved_state = rc.save().expect("save should succeed");
1632
1633        // Create a new root complex with different bus range
1634        let mut rc2 = instantiate_root_complex(0, 127, 2);
1635
1636        // Restore should fail because bus numbers don't match
1637        let result = rc2.restore(saved_state);
1638        assert!(result.is_err());
1639    }
1640
1641    #[test]
1642    fn test_save_restore_port_count_mismatch_error() {
1643        use vmcore::save_restore::SaveRestore;
1644
1645        // Create a root complex with 4 ports
1646        let mut rc = instantiate_root_complex(0, 255, 4);
1647
1648        // Save the state
1649        let saved_state = rc.save().expect("save should succeed");
1650        assert_eq!(saved_state.ports.len(), 4);
1651
1652        // Create a new root complex with only 2 ports
1653        let mut rc2 = instantiate_root_complex(0, 255, 2);
1654
1655        // Restore should fail because port counts don't match
1656        let result = rc2.restore(saved_state);
1657        assert!(result.is_err());
1658    }
1659
1660    #[test]
1661    fn test_save_restore_with_cxl_component_registers() {
1662        use vmcore::save_restore::SaveRestore;
1663
1664        let chbcr_start = 0x3004_0000;
1665        let mut rc = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1666
1667        assert!(
1668            rc.cxl_component_registers
1669                .as_mut()
1670                .expect("CXL mode must allocate component registers")
1671                .add_register(Box::new(TestCxlComponentRegisterBlock::new(
1672                    CxlComponentRegisterType::CXL_CACHE_MEM_REGISTER,
1673                    16,
1674                )))
1675        );
1676
1677        let programmed = 0x3344_5566u32;
1678        rc.mmio_write(chbcr_start + 0x1008, programmed.as_bytes())
1679            .unwrap();
1680
1681        let saved_state = rc.save().expect("save should succeed");
1682
1683        let mut rc2 = instantiate_root_complex_with_chbcr(0, 0, 1, chbcr_start);
1684        assert!(
1685            rc2.cxl_component_registers
1686                .as_mut()
1687                .expect("CXL mode must allocate component registers")
1688                .add_register(Box::new(TestCxlComponentRegisterBlock::new(
1689                    CxlComponentRegisterType::CXL_CACHE_MEM_REGISTER,
1690                    16,
1691                )))
1692        );
1693
1694        rc2.restore(saved_state).expect("restore should succeed");
1695
1696        let mut read_back: u32 = 0;
1697        rc2.mmio_read(chbcr_start + 0x1008, read_back.as_mut_bytes())
1698            .unwrap();
1699        assert_eq!(read_back, programmed);
1700    }
1701
1702    #[test]
1703    fn test_bus_range_updated_on_cfg_write() {
1704        const SECONDARY_BUS_NUM_REG: u64 = 0x19;
1705        const SUBORDINATE_BUS_NUM_REG: u64 = 0x1A;
1706
1707        let mut rc = instantiate_root_complex(0, 255, 1);
1708
1709        let endpoint = TestPcieEndpoint::new(
1710            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1711            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
1712        );
1713
1714        // Get the bus_range from the port before attaching a device.
1715        let bus_range = rc.downstream_ports().into_iter().next().unwrap().bus_range;
1716        assert_eq!(bus_range.bus_range(), (0, 0));
1717
1718        rc.add_pcie_device(0, "ep", Box::new(endpoint)).unwrap();
1719
1720        // Program secondary=5, subordinate=10 via ECAM MMIO writes.
1721        rc.mmio_write(SECONDARY_BUS_NUM_REG, &[5]).unwrap();
1722        rc.mmio_write(SUBORDINATE_BUS_NUM_REG, &[10]).unwrap();
1723
1724        // The shared AssignedBusRange should reflect the new values.
1725        assert_eq!(bus_range.bus_range(), (5, 10));
1726
1727        // Reprogram bus numbers and verify tracking follows.
1728        rc.mmio_write(SECONDARY_BUS_NUM_REG, &[20]).unwrap();
1729        rc.mmio_write(SUBORDINATE_BUS_NUM_REG, &[30]).unwrap();
1730        assert_eq!(bus_range.bus_range(), (20, 30));
1731    }
1732
1733    #[test]
1734    fn test_rciep_ecam_read_write() {
1735        // Create a root complex with root ports starting at device 1,
1736        // leaving device 0 free for an RCiEP.
1737        let mut register_mmio = TestPcieMmioRegistration {};
1738        let start_bus: u8 = 0;
1739        let end_bus: u8 = 0;
1740        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(start_bus, end_bus));
1741        let rc_bus_range = AssignedBusRange::new();
1742        rc_bus_range.set_bus_range(start_bus, end_bus);
1743        let msi_conn = pci_core::msi::MsiConnection::new(rc_bus_range, 0);
1744        let port_defs = vec![GenericPcieRootPortDefinition {
1745            name: "port-0".into(),
1746            hotplug: false,
1747            settings: PciePortSettings::default(),
1748        }];
1749        let mut rc = GenericPcieRootComplex::builder(&mut register_mmio, start_bus..=end_bus, ecam)
1750            .root_ports(port_defs, msi_conn.target())
1751            .first_port_device_number(1)
1752            .build()
1753            .unwrap();
1754
1755        // Attach an RCiEP at device 0 function 0 (devfn 0).
1756        let rciep = TestPcieEndpoint::new(
1757            |offset, value| {
1758                if offset == 0 {
1759                    *value = 0xDEAD_BEEF;
1760                }
1761                Some(IoResult::Ok)
1762            },
1763            |_, _| Some(IoResult::Ok),
1764        );
1765        rc.add_rciep(0, "rciep-0", Box::new(rciep)).unwrap();
1766
1767        // ECAM read at device 0, function 0 should hit the RCiEP.
1768        let mut vendor_device: u32 = 0;
1769        rc.mmio_read(0, vendor_device.as_mut_bytes()).unwrap();
1770        assert_eq!(vendor_device, 0xDEAD_BEEF);
1771
1772        // ECAM write at device 0, function 0 should route to the RCiEP
1773        // (the test endpoint accepts all writes).
1774        rc.mmio_write(0, &0x1234_5678u32.to_le_bytes()).unwrap();
1775
1776        // Root port at device 1 should still be accessible.
1777        let mut root_port_vendor: u32 = 0;
1778        // device 1, function 0 → devfn 8 → offset 8 * 4096
1779        rc.mmio_read(8 * 4096, root_port_vendor.as_mut_bytes())
1780            .unwrap();
1781        assert_eq!(root_port_vendor, 0xC030_1414);
1782
1783        // An unoccupied device slot should return all-1s.
1784        let mut empty: u32 = 0;
1785        // device 2, function 0 → devfn 16 → offset 16 * 4096
1786        rc.mmio_read(16 * 4096, empty.as_mut_bytes()).unwrap();
1787        assert_eq!(empty, 0xFFFF_FFFF);
1788    }
1789
1790    #[test]
1791    fn test_rciep_function0_fallback() {
1792        // Test that a config read to function 1 of an RCiEP device falls
1793        // back to the function-0 device via pci_cfg_read_with_routing,
1794        // which by default returns all-1s for non-zero functions.
1795        let mut register_mmio = TestPcieMmioRegistration {};
1796        let start_bus: u8 = 0;
1797        let end_bus: u8 = 0;
1798        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(start_bus, end_bus));
1799        let mut rc = GenericPcieRootComplex::builder(&mut register_mmio, start_bus..=end_bus, ecam)
1800            .build()
1801            .unwrap();
1802
1803        let rciep = TestPcieEndpoint::new(
1804            |offset, value| {
1805                if offset == 0 {
1806                    *value = 0xCAFE_F00D;
1807                }
1808                Some(IoResult::Ok)
1809            },
1810            |_, _| Some(IoResult::Ok),
1811        );
1812        rc.add_rciep(0, "rciep-0", Box::new(rciep)).unwrap();
1813
1814        // Function 0 should return the device's vendor/device ID.
1815        let mut val: u32 = 0;
1816        rc.mmio_read(0, val.as_mut_bytes()).unwrap();
1817        assert_eq!(val, 0xCAFE_F00D);
1818
1819        // Function 1 (devfn 1) falls back to the function-0 device's
1820        // pci_cfg_read_with_routing, which returns all-1s by default.
1821        let mut val_fn1: u32 = 0;
1822        // devfn 1 → offset 1 * 4096
1823        rc.mmio_read(4096, val_fn1.as_mut_bytes()).unwrap();
1824        assert_eq!(val_fn1, 0xFFFF_FFFF);
1825    }
1826
1827    #[test]
1828    fn test_rciep_collision_with_root_port() {
1829        // Verify that adding an RCiEP at a devfn already occupied by a
1830        // root port returns an error with the port's name.
1831        let mut rc = instantiate_root_complex(0, 0, 1);
1832
1833        let rciep = TestPcieEndpoint::new(|_, _| Some(IoResult::Ok), |_, _| Some(IoResult::Ok));
1834        // Root port 0 sits at devfn 0; adding an RCiEP there should fail.
1835        let err = rc
1836            .add_rciep(0, "rciep-collision", Box::new(rciep))
1837            .expect_err("should fail: devfn occupied by root port");
1838        assert_eq!(err.as_ref(), "test-port-0");
1839    }
1840
1841    #[test]
1842    fn test_rciep_collision_with_rciep() {
1843        // Verify that adding two RCiEPs at the same devfn returns an error.
1844        let mut register_mmio = TestPcieMmioRegistration {};
1845        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(0, 0));
1846        let mut rc = GenericPcieRootComplex::builder(&mut register_mmio, 0..=0u8, ecam)
1847            .build()
1848            .unwrap();
1849
1850        let rciep1 = TestPcieEndpoint::new(|_, _| Some(IoResult::Ok), |_, _| Some(IoResult::Ok));
1851        let rciep2 = TestPcieEndpoint::new(|_, _| Some(IoResult::Ok), |_, _| Some(IoResult::Ok));
1852        rc.add_rciep(0, "rciep-first", Box::new(rciep1)).unwrap();
1853        let err = rc
1854            .add_rciep(0, "rciep-second", Box::new(rciep2))
1855            .expect_err("should fail: devfn already has an RCiEP");
1856        assert_eq!(err.as_ref(), "rciep-first");
1857    }
1858
1859    #[test]
1860    fn test_multi_function_header_bit() {
1861        // With >1 port, bit 23 of register 0x0C (header type bit 7) must be set
1862        // to indicate a multi-function device.
1863        let mut rc = instantiate_root_complex(0, 255, 2);
1864        let mut header_type_reg: u32 = 0;
1865        // Register 0x0C for port 0 (devfn 0).
1866        rc.mmio_read(0x0C, header_type_reg.as_mut_bytes()).unwrap();
1867        // Bit 23 = multi-function flag in the header type byte (offset 0x0E).
1868        assert_ne!(
1869            header_type_reg & (1 << 23),
1870            0,
1871            "multi-function bit must be set"
1872        );
1873
1874        // With exactly 1 port, the multi-function bit should NOT be set.
1875        let mut rc_single = instantiate_root_complex(0, 255, 1);
1876        let mut header_single: u32 = 0;
1877        rc_single
1878            .mmio_read(0x0C, header_single.as_mut_bytes())
1879            .unwrap();
1880        assert_eq!(
1881            header_single & (1 << 23),
1882            0,
1883            "single-function: multi-function bit must be clear"
1884        );
1885    }
1886
1887    #[test]
1888    fn test_too_many_ports_returns_error() {
1889        // 257 ports starting at device 0 requires device 32, which is out of range.
1890        let port_defs: Vec<GenericPcieRootPortDefinition> = (0..257)
1891            .map(|i| GenericPcieRootPortDefinition {
1892                name: format!("port-{}", i).into(),
1893                hotplug: false,
1894                settings: PciePortSettings::default(),
1895            })
1896            .collect();
1897        let mut register_mmio = TestPcieMmioRegistration {};
1898        let ecam = MemoryRange::new(0..ecam_size_from_bus_numbers(0, 255));
1899        let rc_bus_range = AssignedBusRange::new();
1900        rc_bus_range.set_bus_range(0, 255);
1901        let msi_conn = pci_core::msi::MsiConnection::new(rc_bus_range, 0);
1902        let result = GenericPcieRootComplex::builder(&mut register_mmio, 0..=255u8, ecam)
1903            .root_ports(port_defs, msi_conn.target())
1904            .build();
1905        assert!(
1906            result.is_err(),
1907            "257 ports should exceed the 256-port limit"
1908        );
1909    }
1910}