Skip to main content

pcie/
switch.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PCI Express switch port emulation.
5//!
6//! This module provides emulation for PCIe switch ports:
7//! - [`UpstreamSwitchPort`]: Connects a switch to its parent (root port or another switch)
8//! - [`DownstreamSwitchPort`]: Connects a switch to its children (endpoints or other switches)
9//!
10//! Both port types implement Type 1 PCI-to-PCI bridge functionality with appropriate
11//! PCIe capabilities indicating their port type.
12
13use crate::DOWNSTREAM_SWITCH_PORT_DEVICE_ID;
14use crate::UPSTREAM_SWITCH_PORT_DEVICE_ID;
15use crate::VENDOR_ID;
16use crate::port::PcieDownstreamPort;
17use crate::port::PciePortSettings;
18use anyhow::Context;
19use chipset_device::ChipsetDevice;
20use chipset_device::io::IoResult;
21use chipset_device::pci::PciConfigSpace;
22use inspect::Inspect;
23use inspect::InspectMut;
24use pci_bus::GenericPciBusDevice;
25use pci_core::capabilities::pci_express::PciExpressCapability;
26use pci_core::cfg_space_emu::ConfigSpaceType1Emulator;
27use pci_core::msi::MsiTarget;
28use pci_core::spec::caps::pci_express::DevicePortType;
29use pci_core::spec::hwid::ClassCode;
30use pci_core::spec::hwid::HardwareIds;
31use pci_core::spec::hwid::ProgrammingInterface;
32use pci_core::spec::hwid::Subclass;
33use std::sync::Arc;
34use vmcore::device_state::ChangeDeviceState;
35
36/// A PCI Express upstream switch port emulator.
37///
38/// An upstream switch port connects a switch to its parent (e.g., root port or another switch).
39/// It appears as a Type 1 PCI-to-PCI bridge with PCIe capability indicating it's an upstream switch port.
40#[derive(Inspect)]
41pub struct UpstreamSwitchPort {
42    cfg_space: ConfigSpaceType1Emulator,
43}
44
45impl UpstreamSwitchPort {
46    /// Constructs a new [`UpstreamSwitchPort`] emulator.
47    pub fn new() -> Self {
48        let cfg_space = ConfigSpaceType1Emulator::new(
49            HardwareIds {
50                vendor_id: VENDOR_ID,
51                device_id: UPSTREAM_SWITCH_PORT_DEVICE_ID,
52                revision_id: 0,
53                prog_if: ProgrammingInterface::NONE,
54                sub_class: Subclass::BRIDGE_PCI_TO_PCI,
55                base_class: ClassCode::BRIDGE,
56                type0_sub_vendor_id: 0,
57                type0_sub_system_id: 0,
58            },
59            vec![Box::new(PciExpressCapability::new(
60                DevicePortType::UpstreamSwitchPort,
61                None,
62            ))],
63            vec![],
64        );
65        Self { cfg_space }
66    }
67
68    /// Get a reference to the configuration space emulator.
69    pub fn cfg_space(&self) -> &ConfigSpaceType1Emulator {
70        &self.cfg_space
71    }
72
73    /// Get a mutable reference to the configuration space emulator.
74    pub fn cfg_space_mut(&mut self) -> &mut ConfigSpaceType1Emulator {
75        &mut self.cfg_space
76    }
77}
78
79/// A PCI Express downstream switch port emulator.
80///
81/// A downstream switch port connects a switch to its children (e.g., endpoints or other switches).
82/// It appears as a Type 1 PCI-to-PCI bridge with PCIe capability indicating it's a downstream switch port.
83#[derive(Inspect)]
84pub struct DownstreamSwitchPort {
85    /// The common PCIe port implementation.
86    #[inspect(flatten)]
87    port: PcieDownstreamPort,
88}
89
90impl DownstreamSwitchPort {
91    /// Constructs a new [`DownstreamSwitchPort`] emulator.
92    ///
93    /// # Arguments
94    /// * `name` - The name for this downstream switch port
95    /// * `multi_function` - Whether this port should have the multi-function flag set (default: false)
96    /// * `hotplug_slot_number` - The slot number for hotplug support. `Some(slot_number)` enables hotplug, `None` disables it
97    /// * `msi_target` - MSI target for interrupt delivery
98    /// * `settings` - Express-level port settings (ACS, etc.)
99    pub fn new(
100        name: impl Into<Arc<str>>,
101        multi_function: Option<bool>,
102        hotplug_slot_number: Option<u32>,
103        msi_target: &MsiTarget,
104        settings: PciePortSettings,
105    ) -> Self {
106        let multi_function = multi_function.unwrap_or(false);
107        let hardware_ids = HardwareIds {
108            vendor_id: VENDOR_ID,
109            device_id: DOWNSTREAM_SWITCH_PORT_DEVICE_ID,
110            revision_id: 0,
111            prog_if: ProgrammingInterface::NONE,
112            sub_class: Subclass::BRIDGE_PCI_TO_PCI,
113            base_class: ClassCode::BRIDGE,
114            type0_sub_vendor_id: 0,
115            type0_sub_system_id: 0,
116        };
117
118        let port = PcieDownstreamPort::new(
119            name.into().to_string(),
120            hardware_ids,
121            DevicePortType::DownstreamSwitchPort,
122            multi_function,
123            hotplug_slot_number,
124            msi_target,
125            settings,
126            None,
127            None,
128        );
129
130        Self { port }
131    }
132
133    /// Get a reference to the configuration space emulator.
134    pub fn cfg_space(&self) -> &ConfigSpaceType1Emulator {
135        &self.port.cfg_space
136    }
137
138    /// Get a mutable reference to the configuration space emulator.
139    pub fn cfg_space_mut(&mut self) -> &mut ConfigSpaceType1Emulator {
140        &mut self.port.cfg_space
141    }
142}
143
144/// A PCI Express switch definition used for creating switch instances.
145pub struct GenericPcieSwitchDefinition {
146    /// The name of the switch.
147    pub name: Arc<str>,
148    /// The number of downstream ports to create.
149    /// TODO: implement physical slot number, link and slot stuff
150    pub downstream_port_count: u8,
151    /// Whether hotplug is enabled for this switch's downstream ports.
152    pub hotplug: bool,
153    /// MSI target from the parent connection. The switch re-derives
154    /// per-port targets using the upstream port's bus range.
155    pub msi_target: MsiTarget,
156    /// Express-level settings for downstream switch ports.
157    pub dsp_settings: PciePortSettings,
158}
159
160/// A PCI Express switch emulator that implements a complete switch with upstream and downstream ports.
161///
162/// A PCIe switch consists of:
163/// - One upstream switch port that connects to the parent (root port or another switch)
164/// - Multiple downstream switch ports that connect to children (endpoints or other switches)
165///
166/// The switch implements routing functionality to forward configuration space accesses
167/// between the upstream and downstream ports based on bus number assignments.
168#[derive(InspectMut)]
169pub struct GenericPcieSwitch {
170    /// The name of this switch instance.
171    name: Arc<str>,
172    /// The upstream switch port that connects to the parent.
173    upstream_port: UpstreamSwitchPort,
174    /// Downstream switch ports indexed by devfn.
175    #[inspect(with = "|x| inspect::iter_by_index(x).map_value(|(_, v)| v)")]
176    downstream_ports: Vec<(Arc<str>, DownstreamSwitchPort)>,
177}
178
179impl GenericPcieSwitch {
180    /// Constructs a new [`GenericPcieSwitch`] emulator.
181    pub fn new(definition: GenericPcieSwitchDefinition) -> Self {
182        let upstream_port = UpstreamSwitchPort::new();
183
184        // If there are multiple downstream ports, they need the multi-function flag set
185        let multi_function = definition.downstream_port_count > 1;
186
187        // Derive per-port MSI targets from the parent's target, using
188        // the upstream port's bus range. When the guest programs the
189        // upstream port's secondary bus, all downstream port MSI RIDs
190        // automatically update.
191        let switch_msi_target = definition
192            .msi_target
193            .with_bus_range(upstream_port.cfg_space().bus_range(), 0);
194
195        let downstream_ports: Vec<(Arc<str>, DownstreamSwitchPort)> = (0..definition
196            .downstream_port_count)
197            .map(|i| {
198                let port_name: Arc<str> = format!("{}-downstream-{}", definition.name, i).into();
199                // Use the port index as the slot number for hotpluggable ports
200                let hotplug_slot_number = if definition.hotplug {
201                    Some((i as u32) + 1)
202                } else {
203                    None
204                };
205                let port_msi_target = switch_msi_target.with_devfn(i);
206                let port = DownstreamSwitchPort::new(
207                    port_name.clone(),
208                    Some(multi_function),
209                    hotplug_slot_number,
210                    &port_msi_target,
211                    definition.dsp_settings.clone(),
212                );
213                (port_name, port)
214            })
215            .collect();
216
217        Self {
218            name: definition.name,
219            upstream_port,
220            downstream_ports,
221        }
222    }
223
224    /// Get the name of this switch.
225    pub fn name(&self) -> &Arc<str> {
226        &self.name
227    }
228
229    /// Get a reference to the upstream switch port.
230    pub fn upstream_port(&self) -> &UpstreamSwitchPort {
231        &self.upstream_port
232    }
233
234    /// Enumerate the downstream ports of the switch.
235    pub fn downstream_ports(&self) -> Vec<crate::root::DownstreamPortInfo> {
236        self.downstream_ports
237            .iter()
238            .enumerate()
239            .map(|(i, (name, dsp))| crate::root::DownstreamPortInfo {
240                devfn: i as u8,
241                name: name.clone(),
242                bus_range: dsp.port.bus_range(),
243            })
244            .collect()
245    }
246
247    /// Route configuration space read to the appropriate port based on addressing.
248    fn route_cfg_read(
249        &mut self,
250        bus: u8,
251        function: u8,
252        cfg_offset: u16,
253        value: &mut u32,
254    ) -> Option<IoResult> {
255        let upstream_bus_range = self.upstream_port.cfg_space().assigned_bus_range();
256
257        // If the bus range is 0..=0, this indicates invalid/uninitialized bus configuration
258        if upstream_bus_range == (0..=0) {
259            return None;
260        }
261
262        // Only handle accesses within our decoded bus range
263        if !upstream_bus_range.contains(&bus) {
264            return None;
265        }
266
267        let secondary_bus = *upstream_bus_range.start();
268
269        // Direct access to downstream switch ports on the secondary bus
270        if bus == secondary_bus {
271            return self.handle_downstream_port_read(function, cfg_offset, value);
272        }
273
274        // Route to downstream ports for further forwarding
275        self.route_read_to_downstream_ports(bus, function, cfg_offset, value)
276    }
277
278    /// Route configuration space write to the appropriate port based on addressing.
279    fn route_cfg_write(
280        &mut self,
281        bus: u8,
282        function: u8,
283        cfg_offset: u16,
284        value: u32,
285    ) -> Option<IoResult> {
286        let upstream_bus_range = self.upstream_port.cfg_space().assigned_bus_range();
287
288        // If the bus range is 0..=0, this indicates invalid/uninitialized bus configuration
289        if upstream_bus_range == (0..=0) {
290            return None;
291        }
292
293        // Only handle accesses within our decoded bus range
294        if !upstream_bus_range.contains(&bus) {
295            return None;
296        }
297
298        let secondary_bus = *upstream_bus_range.start();
299
300        // Direct access to downstream switch ports on the secondary bus
301        if bus == secondary_bus {
302            return self.handle_downstream_port_write(function, cfg_offset, value);
303        }
304
305        // Route to downstream ports for further forwarding
306        self.route_write_to_downstream_ports(bus, function, cfg_offset, value)
307    }
308
309    /// Handle direct configuration space read to downstream switch ports.
310    fn handle_downstream_port_read(
311        &mut self,
312        function: u8,
313        cfg_offset: u16,
314        value: &mut u32,
315    ) -> Option<IoResult> {
316        let (_, downstream_port) = self.downstream_ports.get_mut(function as usize)?;
317        Some(downstream_port.port.cfg_space.read_u32(cfg_offset, value))
318    }
319
320    /// Handle direct configuration space write to downstream switch ports.
321    fn handle_downstream_port_write(
322        &mut self,
323        function: u8,
324        cfg_offset: u16,
325        value: u32,
326    ) -> Option<IoResult> {
327        let (_, downstream_port) = self.downstream_ports.get_mut(function as usize)?;
328        Some(downstream_port.port.cfg_space.write_u32(cfg_offset, value))
329    }
330
331    /// Route configuration space read to downstream ports for further forwarding.
332    fn route_read_to_downstream_ports(
333        &mut self,
334        bus: u8,
335        function: u8,
336        cfg_offset: u16,
337        value: &mut u32,
338    ) -> Option<IoResult> {
339        for (_, downstream_port) in self.downstream_ports.iter_mut() {
340            let downstream_bus_range = downstream_port.cfg_space().assigned_bus_range();
341
342            // Skip downstream ports with invalid/uninitialized bus configuration
343            if downstream_bus_range == (0..=0) {
344                continue;
345            }
346
347            if downstream_bus_range.contains(&bus) {
348                return Some(
349                    downstream_port
350                        .port
351                        .forward_cfg_read_with_routing(&bus, &function, cfg_offset, value),
352                );
353            }
354        }
355
356        // No downstream port could handle this bus number
357        None
358    }
359
360    /// Route configuration space write to downstream ports for further forwarding.
361    fn route_write_to_downstream_ports(
362        &mut self,
363        bus: u8,
364        function: u8,
365        cfg_offset: u16,
366        value: u32,
367    ) -> Option<IoResult> {
368        for (_, downstream_port) in self.downstream_ports.iter_mut() {
369            let downstream_bus_range = downstream_port.cfg_space().assigned_bus_range();
370
371            // Skip downstream ports with invalid/uninitialized bus configuration
372            if downstream_bus_range == (0..=0) {
373                continue;
374            }
375
376            if downstream_bus_range.contains(&bus) {
377                return Some(
378                    downstream_port
379                        .port
380                        .forward_cfg_write_with_routing(&bus, &function, cfg_offset, value),
381                );
382            }
383        }
384
385        // No downstream port could handle this bus number
386        None
387    }
388
389    /// Attach the provided `GenericPciBusDevice` to the port identified.
390    pub fn add_pcie_device(
391        &mut self,
392        port_devfn: u8,
393        name: &str,
394        dev: Box<dyn GenericPciBusDevice>,
395    ) -> anyhow::Result<()> {
396        let (port_name, downstream_port) = self
397            .downstream_ports
398            .get_mut(port_devfn as usize)
399            .ok_or_else(|| anyhow::anyhow!("port devfn {} not found", port_devfn))?;
400        downstream_port
401            .port
402            .add_pcie_device(port_name.as_ref(), name, dev)
403            .context("failed to add PCIe device to downstream port")?;
404        Ok(())
405    }
406}
407
408impl ChangeDeviceState for GenericPcieSwitch {
409    /// No-op start hook: switch state is fully modeled in config-space state.
410    fn start(&mut self) {}
411
412    /// No-op stop hook: no background tasks or external resources to drain.
413    async fn stop(&mut self) {}
414
415    /// Resets upstream and downstream bridge config-space state to power-on defaults.
416    async fn reset(&mut self) {
417        // Reset the upstream port configuration space
418        self.upstream_port.cfg_space.reset();
419
420        // Reset all downstream port configuration spaces
421        for (_, downstream_port) in self.downstream_ports.iter_mut() {
422            downstream_port.port.cfg_space.reset();
423        }
424    }
425}
426
427impl ChipsetDevice for GenericPcieSwitch {
428    /// Exposes this switch as a PCI config-space device to the chipset bus.
429    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
430        Some(self)
431    }
432}
433
434impl PciConfigSpace for GenericPcieSwitch {
435    /// Reads the switch's own upstream-port config space (Type 0 view).
436    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
437        // Forward to the upstream port's configuration space (the switch presents as the upstream port)
438        self.upstream_port.cfg_space.read_u32(offset, value)
439    }
440
441    /// Writes the switch's own upstream-port config space (Type 0 view).
442    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
443        // Forward to the upstream port's configuration space (the switch presents as the upstream port)
444        self.upstream_port.cfg_space.write_u32(offset, value)
445    }
446
447    fn pci_cfg_read_with_routing(
448        &mut self,
449        secondary_bus: u8,
450        target_bus: u8,
451        function: u8,
452        offset: u16,
453        value: &mut u32,
454    ) -> IoResult {
455        // Try switch-internal routing first (downstream ports and their descendants).
456        if let Some(result) = self.route_cfg_read(target_bus, function, offset, value) {
457            return result;
458        }
459
460        // Routing didn't handle this access. If target_bus equals the
461        // secondary_bus passed by the parent port, this is a Type 0 access
462        // targeting the switch's own upstream port config space.
463        if target_bus == secondary_bus {
464            if function == 0 {
465                return self.upstream_port.cfg_space.read_u32(offset, value);
466            }
467        }
468
469        // No device at this function / bus — return all-1s.
470        *value = !0;
471        IoResult::Ok
472    }
473
474    fn pci_cfg_write_with_routing(
475        &mut self,
476        secondary_bus: u8,
477        target_bus: u8,
478        function: u8,
479        offset: u16,
480        value: u32,
481    ) -> IoResult {
482        // Try switch-internal routing first (downstream ports and their descendants).
483        if let Some(result) = self.route_cfg_write(target_bus, function, offset, value) {
484            return result;
485        }
486
487        // Routing didn't handle this access. If target_bus equals the
488        // secondary_bus passed by the parent port, this is a Type 0 access
489        // targeting the switch's own upstream port config space.
490        if target_bus == secondary_bus {
491            if function == 0 {
492                return self.upstream_port.cfg_space.write_u32(offset, value);
493            }
494        }
495
496        IoResult::Ok
497    }
498
499    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
500        // PCIe switches typically don't have a fixed BDF requirement
501        None
502    }
503}
504
505mod save_restore {
506    use super::*;
507    use std::collections::HashSet;
508    use vmcore::save_restore::RestoreError;
509    use vmcore::save_restore::SaveError;
510    use vmcore::save_restore::SaveRestore;
511
512    mod state {
513        use super::ConfigSpaceType1Emulator;
514        use super::SaveRestore;
515        use cxl_spec::CxlComponentRegisters;
516        use mesh::payload::Protobuf;
517        use vmcore::save_restore::SavedStateRoot;
518
519        type SwitchPortCfgSpaceSavedState = <ConfigSpaceType1Emulator as SaveRestore>::SavedState;
520        type CxlComponentRegistersSavedState = <CxlComponentRegisters as SaveRestore>::SavedState;
521
522        /// Saved state for one switch port config space.
523        #[derive(Protobuf)]
524        #[mesh(package = "pcie.switch")]
525        pub struct DownstreamPortSavedState {
526            /// The devfn of this downstream port.
527            #[mesh(1)]
528            pub devfn: u8,
529            /// The downstream port Type 1 configuration space state.
530            #[mesh(2)]
531            pub cfg_space: SwitchPortCfgSpaceSavedState,
532            /// Optional CXL component-register state for this downstream port.
533            #[mesh(3)]
534            pub cxl_component_registers: Option<CxlComponentRegistersSavedState>,
535        }
536
537        /// Saved state for the GenericPcieSwitch.
538        #[derive(Protobuf, SavedStateRoot)]
539        #[mesh(package = "pcie.switch")]
540        pub struct SavedState {
541            /// The upstream port configuration space state.
542            #[mesh(1)]
543            pub upstream_cfg_space: SwitchPortCfgSpaceSavedState,
544            /// Saved state for downstream ports.
545            ///
546            /// `devfn` identifies the target port for each entry.
547            /// The vector ordering is not part of the saved-state contract.
548            #[mesh(2)]
549            pub downstream_ports: Vec<DownstreamPortSavedState>,
550        }
551    }
552
553    impl SaveRestore for GenericPcieSwitch {
554        type SavedState = state::SavedState;
555
556        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
557            // Save the upstream port configuration space
558            let upstream_cfg_space = self.upstream_port.cfg_space.save()?;
559
560            // Save all downstream ports (already sorted by devfn in the Vec).
561            let mut downstream_ports = Vec::with_capacity(self.downstream_ports.len());
562            for (i, (_, downstream_port)) in self.downstream_ports.iter_mut().enumerate() {
563                downstream_ports.push(state::DownstreamPortSavedState {
564                    devfn: i as u8,
565                    cfg_space: downstream_port.port.cfg_space.save()?,
566                    cxl_component_registers: downstream_port
567                        .port
568                        .save_cxl_component_registers_state()?,
569                });
570            }
571
572            Ok(state::SavedState {
573                upstream_cfg_space,
574                downstream_ports,
575            })
576        }
577
578        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
579            let state::SavedState {
580                upstream_cfg_space,
581                downstream_ports,
582            } = state;
583
584            // Reject snapshots from a different topology shape.
585            if downstream_ports.len() != self.downstream_ports.len() {
586                return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
587                    "downstream port count mismatch: saved {}, current {}",
588                    downstream_ports.len(),
589                    self.downstream_ports.len()
590                )));
591            }
592
593            // Restore the upstream port configuration space
594            self.upstream_port.cfg_space.restore(upstream_cfg_space)?;
595
596            let mut seen_ports = HashSet::with_capacity(downstream_ports.len());
597
598            // Restore all downstream ports by devfn index.
599            for port_state in downstream_ports {
600                // Duplicate entries indicate corrupted or malformed saved state.
601                if !seen_ports.insert(port_state.devfn) {
602                    return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
603                        "duplicate downstream port devfn {} in saved state",
604                        port_state.devfn
605                    )));
606                }
607
608                if let Some((_, downstream_port)) =
609                    self.downstream_ports.get_mut(port_state.devfn as usize)
610                {
611                    downstream_port
612                        .port
613                        .cfg_space
614                        .restore(port_state.cfg_space)?;
615                    downstream_port.port.restore_cxl_component_registers_state(
616                        port_state.cxl_component_registers,
617                    )?;
618                } else {
619                    return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
620                        "downstream port devfn {} not found",
621                        port_state.devfn
622                    )));
623                }
624            }
625
626            Ok(())
627        }
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634    use pci_core::bus_range::AssignedBusRange;
635    use pci_core::msi::MsiConnection;
636
637    #[test]
638    fn test_upstream_switch_port_creation() {
639        let port = UpstreamSwitchPort::new();
640
641        // Verify that we can read the vendor/device ID from config space
642        let mut vendor_device_id: u32 = 0;
643        port.cfg_space.read_u32(0x0, &mut vendor_device_id).unwrap();
644        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
645        assert_eq!(vendor_device_id, expected);
646    }
647
648    #[test]
649    fn test_downstream_switch_port_creation() {
650        let port = DownstreamSwitchPort::new(
651            "test-downstream-port",
652            None,
653            None,
654            &MsiTarget::disconnected(),
655            PciePortSettings::default(),
656        );
657        assert!(port.port.link.is_none());
658
659        // Verify that we can read the vendor/device ID from config space
660        let mut vendor_device_id: u32 = 0;
661        port.port
662            .cfg_space
663            .read_u32(0x0, &mut vendor_device_id)
664            .unwrap();
665        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
666        assert_eq!(vendor_device_id, expected);
667    }
668
669    #[test]
670    fn test_downstream_switch_port_multi_function_options() {
671        // Test with default multi_function (false)
672        let port_default = DownstreamSwitchPort::new(
673            "test-port-default",
674            None,
675            None,
676            &MsiTarget::disconnected(),
677            PciePortSettings::default(),
678        );
679        let mut header_type_value: u32 = 0;
680        port_default
681            .cfg_space()
682            .read_u32(0x0C, &mut header_type_value)
683            .unwrap();
684        let header_type_field = (header_type_value >> 16) & 0xFF;
685        assert_eq!(
686            header_type_field & 0x80,
687            0x00,
688            "Multi-function bit should NOT be set with None parameter"
689        );
690
691        // Test with explicit multi_function false
692        let port_false = DownstreamSwitchPort::new(
693            "test-port-false",
694            Some(false),
695            None,
696            &MsiTarget::disconnected(),
697            PciePortSettings::default(),
698        );
699        let mut header_type_value_false: u32 = 0;
700        port_false
701            .cfg_space()
702            .read_u32(0x0C, &mut header_type_value_false)
703            .unwrap();
704        let header_type_field_false = (header_type_value_false >> 16) & 0xFF;
705        assert_eq!(
706            header_type_field_false & 0x80,
707            0x00,
708            "Multi-function bit should NOT be set with Some(false)"
709        );
710
711        // Test with explicit multi_function true
712        let port_true = DownstreamSwitchPort::new(
713            "test-port-true",
714            Some(true),
715            None,
716            &MsiTarget::disconnected(),
717            PciePortSettings::default(),
718        );
719        let mut header_type_value_true: u32 = 0;
720        port_true
721            .cfg_space()
722            .read_u32(0x0C, &mut header_type_value_true)
723            .unwrap();
724        let header_type_field_true = (header_type_value_true >> 16) & 0xFF;
725        assert_eq!(
726            header_type_field_true & 0x80,
727            0x80,
728            "Multi-function bit should be set with Some(true)"
729        );
730    }
731
732    #[test]
733    fn test_downstream_switch_port_hotplug_options() {
734        // Test with hotplug disabled (None)
735        let port_no_hotplug = DownstreamSwitchPort::new(
736            "test-port-no-hotplug",
737            None,
738            None,
739            &MsiTarget::disconnected(),
740            PciePortSettings::default(),
741        );
742        // We can't easily verify hotplug is disabled without accessing internal state,
743        // but we can verify the port was created successfully
744        let mut vendor_device_id: u32 = 0;
745        port_no_hotplug
746            .cfg_space()
747            .read_u32(0x0, &mut vendor_device_id)
748            .unwrap();
749        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
750        assert_eq!(vendor_device_id, expected);
751
752        // Test with hotplug enabled (Some(slot_number))
753        let port_with_hotplug = DownstreamSwitchPort::new(
754            "test-port-hotplug",
755            None,
756            Some(42),
757            &MsiTarget::disconnected(),
758            PciePortSettings::default(),
759        );
760        let mut vendor_device_id_hotplug: u32 = 0;
761        port_with_hotplug
762            .cfg_space()
763            .read_u32(0x0, &mut vendor_device_id_hotplug)
764            .unwrap();
765        assert_eq!(vendor_device_id_hotplug, expected);
766        // The slot number and hotplug capability would be tested via PCIe capability registers
767        // but that requires more complex setup
768    }
769
770    #[test]
771    fn test_switch_creation() {
772        let definition = GenericPcieSwitchDefinition {
773            name: "test-switch".into(),
774            downstream_port_count: 3,
775            hotplug: false,
776            dsp_settings: PciePortSettings::default(),
777            msi_target: MsiTarget::disconnected(),
778        };
779        let switch = GenericPcieSwitch::new(definition);
780
781        assert_eq!(switch.name().as_ref(), "test-switch");
782        assert_eq!(switch.downstream_ports().len(), 3);
783
784        // Verify downstream port names (HashMap doesn't guarantee order, so check each one exists)
785        let ports = switch.downstream_ports();
786        let port_names: std::collections::HashSet<_> =
787            ports.iter().map(|p| p.name.as_ref()).collect();
788        assert!(port_names.contains("test-switch-downstream-0"));
789        assert!(port_names.contains("test-switch-downstream-1"));
790        assert!(port_names.contains("test-switch-downstream-2"));
791
792        // Verify port numbers
793        let port_numbers: std::collections::HashSet<_> = ports.iter().map(|p| p.devfn).collect();
794        assert!(port_numbers.contains(&0));
795        assert!(port_numbers.contains(&1));
796        assert!(port_numbers.contains(&2));
797    }
798
799    #[test]
800    fn test_switch_device_connections() {
801        use crate::test_helpers::TestPcieEndpoint;
802        use chipset_device::io::IoError;
803
804        let definition = GenericPcieSwitchDefinition {
805            name: "test-switch".into(),
806            downstream_port_count: 2,
807            hotplug: false,
808            dsp_settings: PciePortSettings::default(),
809            msi_target: MsiTarget::disconnected(),
810        };
811        let mut switch = GenericPcieSwitch::new(definition);
812
813        let downstream_device = TestPcieEndpoint::new(
814            |offset, value| match offset {
815                0x0 => {
816                    *value = 0xABCD_EF01;
817                    Some(IoResult::Ok)
818                }
819                _ => Some(IoResult::Err(IoError::InvalidRegister)),
820            },
821            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
822        );
823
824        // Connect downstream device to port 0
825        assert!(
826            switch
827                .add_pcie_device(
828                    0, // Port number instead of port name
829                    "downstream-dev",
830                    Box::new(downstream_device),
831                )
832                .is_ok()
833        );
834
835        // Try to connect to invalid port
836        let invalid_device = TestPcieEndpoint::new(
837            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
838            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
839        );
840        let result = switch.add_pcie_device(99, "invalid-dev", Box::new(invalid_device)); // Use invalid port number
841        assert!(result.is_err());
842        // add_pcie_device returns an anyhow::Error on failure,
843        // so we just verify that the connection failed
844        assert!(result.is_err());
845    }
846
847    #[test]
848    fn test_switch_routing_functionality() {
849        use crate::test_helpers::TestPcieEndpoint;
850        use chipset_device::io::IoResult;
851
852        let definition = GenericPcieSwitchDefinition {
853            name: "test-switch".into(),
854            downstream_port_count: 2,
855            hotplug: false,
856            dsp_settings: PciePortSettings::default(),
857            msi_target: MsiTarget::disconnected(),
858        };
859        let mut switch = GenericPcieSwitch::new(definition);
860
861        // Verify that Switch implements routing functionality by testing add_pcie_device method
862        // This tests that the switch can accept device connections (routing capability)
863        let test_device =
864            TestPcieEndpoint::new(|_, _| Some(IoResult::Ok), |_, _| Some(IoResult::Ok));
865        let add_result = switch.add_pcie_device(0, "test-device", Box::new(test_device));
866        // Should succeed for port 0 (first downstream port)
867        assert!(add_result.is_ok());
868
869        // Test basic configuration space access through the PCI interface
870        let mut value = 0u32;
871        let result = switch
872            .upstream_port
873            .cfg_space_mut()
874            .read_u32(0x0, &mut value);
875        assert!(matches!(result, IoResult::Ok));
876
877        // Verify vendor/device ID is from the upstream port
878        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
879        assert_eq!(value, expected);
880    }
881
882    #[test]
883    fn test_switch_chipset_device() {
884        use chipset_device::ChipsetDevice;
885        use chipset_device::pci::PciConfigSpace;
886
887        let definition = GenericPcieSwitchDefinition {
888            name: "test-switch".into(),
889            downstream_port_count: 4,
890            hotplug: false,
891            dsp_settings: PciePortSettings::default(),
892            msi_target: MsiTarget::disconnected(),
893        };
894        let mut switch = GenericPcieSwitch::new(definition);
895
896        // Test that it supports PCI but not other interfaces
897        assert!(switch.supports_pci().is_some());
898        assert!(switch.supports_mmio().is_none());
899        assert!(switch.supports_pio().is_none());
900        assert!(switch.supports_poll_device().is_none());
901
902        // Test PciConfigSpace interface
903        let mut value = 0u32;
904        let result = PciConfigSpace::pci_cfg_read(&mut switch, 0x0, &mut value);
905        assert!(matches!(result, IoResult::Ok));
906
907        // Verify we get the expected vendor/device ID
908        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
909        assert_eq!(value, expected);
910
911        // Test write operation
912        let result = PciConfigSpace::pci_cfg_write(&mut switch, 0x4, 0x12345678);
913        assert!(matches!(result, IoResult::Ok));
914    }
915
916    #[test]
917    fn test_switch_default() {
918        let definition = GenericPcieSwitchDefinition {
919            name: "default-switch".into(),
920            downstream_port_count: 4,
921            hotplug: false,
922            dsp_settings: PciePortSettings::default(),
923            msi_target: MsiTarget::disconnected(),
924        };
925        let switch = GenericPcieSwitch::new(definition);
926        assert_eq!(switch.name().as_ref(), "default-switch");
927        assert_eq!(switch.downstream_ports().len(), 4);
928    }
929
930    #[test]
931    fn test_switch_large_downstream_port_count() {
932        let definition = GenericPcieSwitchDefinition {
933            name: "test-switch".into(),
934            downstream_port_count: 16,
935            hotplug: false,
936            dsp_settings: PciePortSettings::default(),
937            msi_target: MsiTarget::disconnected(),
938        };
939        let switch = GenericPcieSwitch::new(definition);
940        assert_eq!(switch.downstream_ports().len(), 16);
941    }
942
943    #[test]
944    fn test_switch_downstream_port_direct_access() {
945        let definition = GenericPcieSwitchDefinition {
946            name: "test-switch".into(),
947            downstream_port_count: 3,
948            hotplug: false,
949            dsp_settings: PciePortSettings::default(),
950            msi_target: MsiTarget::disconnected(),
951        };
952        let mut switch = GenericPcieSwitch::new(definition);
953
954        // Simulate the switch's internal bus being assigned as bus 1
955        let secondary_bus = 1u8;
956        // Set secondary bus number (offset 0x18) - bits 8-15 of the 32-bit value at 0x18
957        let bus_config = (10u32 << 24) | ((secondary_bus as u32) << 16); // subordinate | secondary
958        switch
959            .upstream_port
960            .cfg_space_mut()
961            .write_u32(0x18, bus_config)
962            .unwrap();
963
964        let bus_range = switch.upstream_port.cfg_space().assigned_bus_range();
965        let switch_internal_bus = *bus_range.start(); // This is the secondary bus
966
967        // Test direct access to downstream port 0 using function = 0
968        let mut value = 0u32;
969        let result = switch.route_cfg_read(switch_internal_bus, 0, 0x0, &mut value);
970        assert!(result.is_some());
971
972        // Verify we got the downstream switch port's vendor/device ID
973        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
974        assert_eq!(value, expected);
975
976        // Test direct access to downstream port 2 using function = 2
977        let mut value2 = 0u32;
978        let result2 = switch.route_cfg_read(switch_internal_bus, 2, 0x0, &mut value2);
979        assert!(result2.is_some());
980        assert_eq!(value2, expected);
981
982        // Test access to non-existent downstream port using function = 5
983        let mut value3 = 0u32;
984        let result3 = switch.route_cfg_read(switch_internal_bus, 5, 0x0, &mut value3);
985        assert!(result3.is_none());
986    }
987
988    #[test]
989    fn test_switch_invalid_bus_range_handling() {
990        let definition = GenericPcieSwitchDefinition {
991            name: "test-switch".into(),
992            downstream_port_count: 2,
993            hotplug: false,
994            dsp_settings: PciePortSettings::default(),
995            msi_target: MsiTarget::disconnected(),
996        };
997        let mut switch = GenericPcieSwitch::new(definition);
998
999        // Don't configure bus numbers, so the range should be 0..=0 (invalid)
1000        let bus_range = switch.upstream_port.cfg_space().assigned_bus_range();
1001        assert_eq!(bus_range, 0..=0);
1002
1003        // Test that any access returns None when bus range is invalid
1004        let mut value = 0u32;
1005        let result = switch.route_cfg_read(0, 0, 0x0, &mut value);
1006        assert!(result.is_none());
1007
1008        let result2 = switch.route_cfg_read(1, 0, 0x0, &mut value);
1009        assert!(result2.is_none());
1010
1011        let result3 = switch.route_cfg_write(0, 0, 0x0, value);
1012        assert!(result3.is_none());
1013    }
1014
1015    #[test]
1016    fn test_switch_downstream_port_invalid_bus_range_skipping() {
1017        let definition = GenericPcieSwitchDefinition {
1018            name: "test-switch".into(),
1019            downstream_port_count: 2,
1020            hotplug: false,
1021            dsp_settings: PciePortSettings::default(),
1022            msi_target: MsiTarget::disconnected(),
1023        };
1024        let mut switch = GenericPcieSwitch::new(definition);
1025
1026        // Configure the upstream port with a valid bus range
1027        let secondary_bus = 1u8;
1028        let subordinate_bus = 10u8;
1029        let primary_bus = 0u8;
1030        let bus_config =
1031            ((subordinate_bus as u32) << 16) | ((secondary_bus as u32) << 8) | (primary_bus as u32); // subordinate | secondary | primary
1032        switch
1033            .upstream_port
1034            .cfg_space_mut()
1035            .write_u32(0x18, bus_config)
1036            .unwrap();
1037
1038        // Downstream ports still have invalid bus ranges (0..=0 by default)
1039        // so any access to buses beyond the secondary bus should return None
1040        let mut value = 0u32;
1041
1042        // Access to bus 2 should return None since no downstream port has a valid bus range
1043        let result = switch.route_cfg_read(2, 0, 0x0, &mut value);
1044        assert!(result.is_none());
1045
1046        // Access to bus 5 should also return None
1047        let result2 = switch.route_cfg_read(5, 0, 0x0, &mut value);
1048        assert!(result2.is_none());
1049
1050        // Access to the secondary bus (switch internal) should still work for downstream port config
1051        let result3 = switch.route_cfg_read(secondary_bus, 0, 0x0, &mut value);
1052        assert!(result3.is_some());
1053    }
1054
1055    #[test]
1056    fn test_switch_multi_function_bit() {
1057        // Test that switches with multiple downstream ports set the multi-function bit
1058        let multi_port_definition = GenericPcieSwitchDefinition {
1059            name: "multi-port-switch".into(),
1060            downstream_port_count: 3,
1061            hotplug: false,
1062            dsp_settings: PciePortSettings::default(),
1063            msi_target: MsiTarget::disconnected(),
1064        };
1065        let multi_port_switch = GenericPcieSwitch::new(multi_port_definition);
1066
1067        // Verify each downstream port has the multi-function bit set
1068        for p in multi_port_switch.downstream_ports() {
1069            let port_num = p.devfn;
1070            if let Some((_, downstream_port)) =
1071                multi_port_switch.downstream_ports.get(port_num as usize)
1072            {
1073                let mut header_type_value: u32 = 0;
1074                downstream_port
1075                    .cfg_space()
1076                    .read_u32(0x0C, &mut header_type_value)
1077                    .unwrap();
1078
1079                // Extract the header type field (bits 16-23, with multi-function bit at bit 23)
1080                let header_type_field = (header_type_value >> 16) & 0xFF;
1081
1082                // Multi-function bit should be set (bit 7 of header type field = bit 23 of dword)
1083                assert_eq!(
1084                    header_type_field & 0x80,
1085                    0x80,
1086                    "Multi-function bit should be set for downstream port {} in multi-port switch",
1087                    port_num
1088                );
1089
1090                // Base header type should still be 01 (bridge)
1091                assert_eq!(
1092                    header_type_field & 0x7F,
1093                    0x01,
1094                    "Header type should be 01 (bridge) for downstream port {}",
1095                    port_num
1096                );
1097            }
1098        }
1099
1100        // Test that switches with single downstream port do NOT set the multi-function bit
1101        let single_port_definition = GenericPcieSwitchDefinition {
1102            name: "single-port-switch".into(),
1103            downstream_port_count: 1,
1104            hotplug: false,
1105            dsp_settings: PciePortSettings::default(),
1106            msi_target: MsiTarget::disconnected(),
1107        };
1108        let single_port_switch = GenericPcieSwitch::new(single_port_definition);
1109
1110        // Verify the single downstream port does NOT have the multi-function bit set
1111        for p in single_port_switch.downstream_ports() {
1112            let port_num = p.devfn;
1113            if let Some((_, downstream_port)) =
1114                single_port_switch.downstream_ports.get(port_num as usize)
1115            {
1116                let mut header_type_value: u32 = 0;
1117                downstream_port
1118                    .cfg_space()
1119                    .read_u32(0x0C, &mut header_type_value)
1120                    .unwrap();
1121
1122                // Extract the header type field (bits 16-23)
1123                let header_type_field = (header_type_value >> 16) & 0xFF;
1124
1125                // Multi-function bit should NOT be set
1126                assert_eq!(
1127                    header_type_field & 0x80,
1128                    0x00,
1129                    "Multi-function bit should NOT be set for downstream port {} in single-port switch",
1130                    port_num
1131                );
1132
1133                // Base header type should still be 01 (bridge)
1134                assert_eq!(
1135                    header_type_field & 0x7F,
1136                    0x01,
1137                    "Header type should be 01 (bridge) for downstream port {}",
1138                    port_num
1139                );
1140            }
1141        }
1142    }
1143
1144    #[test]
1145    fn test_hotplug_support() {
1146        // Test hotplug disabled
1147        let definition_no_hotplug = GenericPcieSwitchDefinition {
1148            name: "test-switch-no-hotplug".into(),
1149            downstream_port_count: 1,
1150            hotplug: false,
1151            dsp_settings: PciePortSettings::default(),
1152            msi_target: MsiTarget::disconnected(),
1153        };
1154        let switch_no_hotplug = GenericPcieSwitch::new(definition_no_hotplug);
1155        assert_eq!(switch_no_hotplug.name().as_ref(), "test-switch-no-hotplug");
1156
1157        // Test hotplug enabled
1158        let definition_with_hotplug = GenericPcieSwitchDefinition {
1159            name: "test-switch-with-hotplug".into(),
1160            downstream_port_count: 1,
1161            hotplug: true,
1162            dsp_settings: PciePortSettings::default(),
1163            msi_target: MsiTarget::disconnected(),
1164        };
1165        let switch_with_hotplug = GenericPcieSwitch::new(definition_with_hotplug);
1166        assert_eq!(
1167            switch_with_hotplug.name().as_ref(),
1168            "test-switch-with-hotplug"
1169        );
1170    }
1171
1172    #[test]
1173    fn test_save_restore_port_mismatch_error() {
1174        use vmcore::save_restore::SaveRestore;
1175
1176        // Create a switch with 3 downstream ports
1177        let definition = GenericPcieSwitchDefinition {
1178            name: "test-switch".into(),
1179            downstream_port_count: 3,
1180            hotplug: false,
1181            dsp_settings: PciePortSettings::default(),
1182            msi_target: MsiTarget::disconnected(),
1183        };
1184        let mut switch = GenericPcieSwitch::new(definition);
1185
1186        // Save the state
1187        let saved_state = switch.save().expect("save should succeed");
1188        assert_eq!(saved_state.downstream_ports.len(), 3);
1189
1190        // Create a new switch with only 2 downstream ports
1191        let definition2 = GenericPcieSwitchDefinition {
1192            name: "test-switch".into(),
1193            downstream_port_count: 2,
1194            hotplug: false,
1195            dsp_settings: PciePortSettings::default(),
1196            msi_target: MsiTarget::disconnected(),
1197        };
1198        let mut switch2 = GenericPcieSwitch::new(definition2);
1199
1200        // Restore should fail because port 2 doesn't exist in the new switch
1201        let result = switch2.restore(saved_state);
1202        assert!(result.is_err());
1203    }
1204
1205    #[test]
1206    fn test_save_restore_basic() {
1207        use vmcore::save_restore::SaveRestore;
1208
1209        let definition = GenericPcieSwitchDefinition {
1210            name: "test-switch".into(),
1211            downstream_port_count: 2,
1212            hotplug: false,
1213            dsp_settings: PciePortSettings::default(),
1214            msi_target: MsiTarget::disconnected(),
1215        };
1216        let mut switch = GenericPcieSwitch::new(definition);
1217
1218        // Save the initial state
1219        let saved_state = switch.save().expect("save should succeed");
1220
1221        // Verify the saved state has the correct number of downstream ports
1222        assert_eq!(saved_state.downstream_ports.len(), 2);
1223
1224        // Restore the state to a new switch
1225        let definition2 = GenericPcieSwitchDefinition {
1226            name: "test-switch".into(),
1227            downstream_port_count: 2,
1228            hotplug: false,
1229            dsp_settings: PciePortSettings::default(),
1230            msi_target: MsiTarget::disconnected(),
1231        };
1232        let mut switch2 = GenericPcieSwitch::new(definition2);
1233        switch2
1234            .restore(saved_state)
1235            .expect("restore should succeed");
1236    }
1237
1238    #[test]
1239    fn test_save_restore_with_bus_configuration() {
1240        use vmcore::save_restore::SaveRestore;
1241
1242        let definition = GenericPcieSwitchDefinition {
1243            name: "test-switch".into(),
1244            downstream_port_count: 3,
1245            hotplug: false,
1246            dsp_settings: PciePortSettings::default(),
1247            msi_target: MsiTarget::disconnected(),
1248        };
1249        let mut switch = GenericPcieSwitch::new(definition);
1250
1251        // Configure bus numbers on the upstream port
1252        let secondary_bus = 5u8;
1253        let subordinate_bus = 15u8;
1254        let primary_bus = 0u8;
1255        let bus_config =
1256            ((subordinate_bus as u32) << 16) | ((secondary_bus as u32) << 8) | (primary_bus as u32);
1257        switch
1258            .upstream_port
1259            .cfg_space_mut()
1260            .write_u32(0x18, bus_config)
1261            .unwrap();
1262
1263        // Verify the bus range is set
1264        let bus_range = switch.upstream_port.cfg_space().assigned_bus_range();
1265        assert_eq!(*bus_range.start(), secondary_bus);
1266        assert_eq!(*bus_range.end(), subordinate_bus);
1267
1268        // Save the state
1269        let saved_state = switch.save().expect("save should succeed");
1270
1271        // Create a new switch and restore the state
1272        let definition2 = GenericPcieSwitchDefinition {
1273            name: "test-switch".into(),
1274            downstream_port_count: 3,
1275            hotplug: false,
1276            dsp_settings: PciePortSettings::default(),
1277            msi_target: MsiTarget::disconnected(),
1278        };
1279        let mut switch2 = GenericPcieSwitch::new(definition2);
1280
1281        // Verify the new switch has default bus range before restore
1282        let default_bus_range = switch2.upstream_port.cfg_space().assigned_bus_range();
1283        assert_eq!(default_bus_range, 0..=0);
1284
1285        // Restore the state
1286        switch2
1287            .restore(saved_state)
1288            .expect("restore should succeed");
1289
1290        // Verify the bus range is restored
1291        let restored_bus_range = switch2.upstream_port.cfg_space().assigned_bus_range();
1292        assert_eq!(*restored_bus_range.start(), secondary_bus);
1293        assert_eq!(*restored_bus_range.end(), subordinate_bus);
1294    }
1295
1296    #[test]
1297    fn test_save_restore_downstream_port_state() {
1298        use vmcore::save_restore::SaveRestore;
1299
1300        let definition = GenericPcieSwitchDefinition {
1301            name: "test-switch".into(),
1302            downstream_port_count: 2,
1303            hotplug: false,
1304            dsp_settings: PciePortSettings::default(),
1305            msi_target: MsiTarget::disconnected(),
1306        };
1307        let mut switch = GenericPcieSwitch::new(definition);
1308
1309        // Configure bus numbers on one of the downstream ports
1310        // First, we need to get access to the downstream port and configure it
1311        if let Some((_, downstream_port)) = switch.downstream_ports.get_mut(0) {
1312            let secondary_bus = 10u8;
1313            let subordinate_bus = 20u8;
1314            let primary_bus = 5u8;
1315            let bus_config = ((subordinate_bus as u32) << 16)
1316                | ((secondary_bus as u32) << 8)
1317                | (primary_bus as u32);
1318            downstream_port
1319                .port
1320                .cfg_space
1321                .write_u32(0x18, bus_config)
1322                .unwrap();
1323        }
1324
1325        // Verify the downstream port bus range is set
1326        if let Some((_, downstream_port)) = switch.downstream_ports.first() {
1327            let bus_range = downstream_port.cfg_space().assigned_bus_range();
1328            assert_eq!(*bus_range.start(), 10);
1329            assert_eq!(*bus_range.end(), 20);
1330        }
1331
1332        // Save the state
1333        let saved_state = switch.save().expect("save should succeed");
1334
1335        // Create a new switch and restore the state
1336        let definition2 = GenericPcieSwitchDefinition {
1337            name: "test-switch".into(),
1338            downstream_port_count: 2,
1339            hotplug: false,
1340            dsp_settings: PciePortSettings::default(),
1341            msi_target: MsiTarget::disconnected(),
1342        };
1343        let mut switch2 = GenericPcieSwitch::new(definition2);
1344
1345        // Verify the new switch has default bus range on downstream port before restore
1346        if let Some((_, downstream_port)) = switch2.downstream_ports.first() {
1347            let default_bus_range = downstream_port.cfg_space().assigned_bus_range();
1348            assert_eq!(default_bus_range, 0..=0);
1349        }
1350
1351        // Restore the state
1352        switch2
1353            .restore(saved_state)
1354            .expect("restore should succeed");
1355
1356        // Verify the downstream port bus range is restored
1357        if let Some((_, downstream_port)) = switch2.downstream_ports.first() {
1358            let restored_bus_range = downstream_port.cfg_space().assigned_bus_range();
1359            assert_eq!(*restored_bus_range.start(), 10);
1360            assert_eq!(*restored_bus_range.end(), 20);
1361        }
1362    }
1363
1364    #[test]
1365    fn test_save_restore_preserves_upstream_and_downstream_cfg_space() {
1366        use vmcore::save_restore::SaveRestore;
1367
1368        let definition = GenericPcieSwitchDefinition {
1369            name: "test-switch".into(),
1370            downstream_port_count: 2,
1371            hotplug: false,
1372            dsp_settings: PciePortSettings::default(),
1373            msi_target: MsiTarget::disconnected(),
1374        };
1375        let mut switch = GenericPcieSwitch::new(definition);
1376
1377        // Upstream primary/secondary/subordinate bus numbers.
1378        switch
1379            .upstream_port
1380            .cfg_space_mut()
1381            .write_u32(0x18, 0x0014_1200)
1382            .unwrap();
1383
1384        // Downstream port 1 bus range.
1385        if let Some((_, downstream_port)) = switch.downstream_ports.get_mut(1) {
1386            downstream_port
1387                .port
1388                .cfg_space
1389                .write_u32(0x18, 0x0020_1f12)
1390                .unwrap();
1391        }
1392
1393        let saved_state = switch.save().expect("save should succeed");
1394
1395        let definition2 = GenericPcieSwitchDefinition {
1396            name: "test-switch".into(),
1397            downstream_port_count: 2,
1398            hotplug: false,
1399            dsp_settings: PciePortSettings::default(),
1400            msi_target: MsiTarget::disconnected(),
1401        };
1402        let mut switch2 = GenericPcieSwitch::new(definition2);
1403
1404        switch2
1405            .restore(saved_state)
1406            .expect("restore should succeed");
1407
1408        let upstream_bus_range = switch2.upstream_port.cfg_space().assigned_bus_range();
1409        assert_eq!(*upstream_bus_range.start(), 0x12);
1410        assert_eq!(*upstream_bus_range.end(), 0x14);
1411
1412        if let Some((_, downstream_port)) = switch2.downstream_ports.get(1) {
1413            let downstream_bus_range = downstream_port.cfg_space().assigned_bus_range();
1414            assert_eq!(*downstream_bus_range.start(), 0x1f);
1415            assert_eq!(*downstream_bus_range.end(), 0x20);
1416        } else {
1417            panic!("missing downstream port 1");
1418        }
1419    }
1420
1421    /// Adapts a `GenericPcieSwitch` to the `GenericPciBusDevice` trait so it
1422    /// can be attached to a downstream port as a linked device in tests.
1423    struct SwitchAdapter(GenericPcieSwitch);
1424
1425    impl GenericPciBusDevice for SwitchAdapter {
1426        fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> Option<IoResult> {
1427            Some(PciConfigSpace::pci_cfg_read(&mut self.0, offset, value))
1428        }
1429
1430        fn pci_cfg_write(&mut self, offset: u16, value: u32) -> Option<IoResult> {
1431            Some(PciConfigSpace::pci_cfg_write(&mut self.0, offset, value))
1432        }
1433
1434        fn pci_cfg_read_with_routing(
1435            &mut self,
1436            secondary_bus: u8,
1437            target_bus: u8,
1438            function: u8,
1439            offset: u16,
1440            value: &mut u32,
1441        ) -> Option<IoResult> {
1442            Some(self.0.pci_cfg_read_with_routing(
1443                secondary_bus,
1444                target_bus,
1445                function,
1446                offset,
1447                value,
1448            ))
1449        }
1450
1451        fn pci_cfg_write_with_routing(
1452            &mut self,
1453            secondary_bus: u8,
1454            target_bus: u8,
1455            function: u8,
1456            offset: u16,
1457            value: u32,
1458        ) -> Option<IoResult> {
1459            Some(self.0.pci_cfg_write_with_routing(
1460                secondary_bus,
1461                target_bus,
1462                function,
1463                offset,
1464                value,
1465            ))
1466        }
1467    }
1468
1469    #[test]
1470    fn test_switch_enumeration_through_port() {
1471        use crate::port::PcieDownstreamPort;
1472        use pci_core::spec::hwid::{ClassCode, ProgrammingInterface, Subclass};
1473
1474        let hardware_ids = HardwareIds {
1475            vendor_id: 0x1234,
1476            device_id: 0x5678,
1477            revision_id: 0,
1478            prog_if: ProgrammingInterface::NONE,
1479            sub_class: Subclass::BRIDGE_PCI_TO_PCI,
1480            base_class: ClassCode::BRIDGE,
1481            type0_sub_vendor_id: 0,
1482            type0_sub_system_id: 0,
1483        };
1484
1485        let msi_conn = MsiConnection::new(AssignedBusRange::new(), 0);
1486        let mut port = PcieDownstreamPort::new(
1487            "root-port",
1488            hardware_ids,
1489            DevicePortType::RootPort,
1490            false,
1491            None,
1492            msi_conn.target(),
1493            PciePortSettings::default(),
1494            None,
1495            None,
1496        );
1497
1498        // Configure the root port's bus range: secondary=1, subordinate=10
1499        port.cfg_space
1500            .write_u32(0x18, (10u32 << 16) | (1u32 << 8))
1501            .unwrap();
1502
1503        // Create and attach a switch behind the port
1504        let switch = GenericPcieSwitch::new(GenericPcieSwitchDefinition {
1505            name: "test-switch".into(),
1506            downstream_port_count: 2,
1507            hotplug: false,
1508            dsp_settings: PciePortSettings::default(),
1509            msi_target: MsiTarget::disconnected(),
1510        });
1511
1512        port.link = Some(("switch".into(), Box::new(SwitchAdapter(switch))));
1513
1514        // Type 0 config read to bus 1 (secondary), function 0 — this should
1515        // read the switch's upstream port config space and return its
1516        // vendor/device ID.
1517        let mut value = 0u32;
1518        let result = port.forward_cfg_read_with_routing(&1, &0, 0x0, &mut value);
1519        assert!(matches!(result, IoResult::Ok));
1520
1521        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
1522        assert_eq!(
1523            value, expected,
1524            "Type 0 access to bus 1 function 0 must read the switch's upstream port"
1525        );
1526
1527        // Non-zero function on the same bus should return no device (switch
1528        // upstream port is single-function).
1529        let mut value2 = 0u32;
1530        let result2 = port.forward_cfg_read_with_routing(&1, &1, 0x0, &mut value2);
1531        assert!(matches!(result2, IoResult::Ok));
1532        assert_eq!(
1533            value2, !0,
1534            "Non-zero function should return all-1s (no device)"
1535        );
1536    }
1537
1538    #[test]
1539    fn test_switch_acs_only_applies_to_dsp() {
1540        use pci_core::spec::caps::ExtendedCapabilityId;
1541
1542        let switch = GenericPcieSwitch::new(GenericPcieSwitchDefinition {
1543            name: "acs-switch".into(),
1544            downstream_port_count: 1,
1545            hotplug: false,
1546            dsp_settings: PciePortSettings {
1547                acs_capabilities_supported: 0x0001,
1548                ..Default::default()
1549            },
1550            msi_target: MsiTarget::disconnected(),
1551        });
1552
1553        // Upstream switch ports do not expose ACS in this model.
1554        let mut upstream_header = 0u32;
1555        switch
1556            .upstream_port()
1557            .cfg_space()
1558            .read_u32(0x100, &mut upstream_header)
1559            .unwrap();
1560        assert_eq!(upstream_header, 0xffff_ffff);
1561
1562        let mut switch = switch;
1563        let (_, downstream_port) = switch
1564            .downstream_ports
1565            .iter_mut()
1566            .next()
1567            .expect("expected downstream port");
1568
1569        let mut downstream_header = 0u32;
1570        downstream_port
1571            .cfg_space_mut()
1572            .read_u32(0x100, &mut downstream_header)
1573            .unwrap();
1574        assert_eq!(
1575            downstream_header & 0xffff,
1576            ExtendedCapabilityId::ACS.0 as u32
1577        );
1578
1579        let mut caps_control = 0u32;
1580        downstream_port
1581            .cfg_space_mut()
1582            .read_u32(0x104, &mut caps_control)
1583            .unwrap();
1584        assert_eq!(caps_control as u16, 0x0001);
1585    }
1586}