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 anyhow::{Context, bail};
18use chipset_device::ChipsetDevice;
19use chipset_device::io::IoResult;
20use chipset_device::pci::PciConfigSpace;
21use inspect::Inspect;
22use inspect::InspectMut;
23use pci_bus::GenericPciBusDevice;
24use pci_core::capabilities::pci_express::PciExpressCapability;
25use pci_core::cfg_space_emu::ConfigSpaceType1Emulator;
26use pci_core::spec::caps::pci_express::DevicePortType;
27use pci_core::spec::hwid::ClassCode;
28use pci_core::spec::hwid::HardwareIds;
29use pci_core::spec::hwid::ProgrammingInterface;
30use pci_core::spec::hwid::Subclass;
31use std::collections::HashMap;
32use std::sync::Arc;
33use vmcore::device_state::ChangeDeviceState;
34
35/// A PCI Express upstream switch port emulator.
36///
37/// An upstream switch port connects a switch to its parent (e.g., root port or another switch).
38/// It appears as a Type 1 PCI-to-PCI bridge with PCIe capability indicating it's an upstream switch port.
39#[derive(Inspect)]
40pub struct UpstreamSwitchPort {
41    cfg_space: ConfigSpaceType1Emulator,
42}
43
44impl UpstreamSwitchPort {
45    /// Constructs a new [`UpstreamSwitchPort`] emulator.
46    pub fn new() -> Self {
47        let cfg_space = ConfigSpaceType1Emulator::new(
48            HardwareIds {
49                vendor_id: VENDOR_ID,
50                device_id: UPSTREAM_SWITCH_PORT_DEVICE_ID,
51                revision_id: 0,
52                prog_if: ProgrammingInterface::NONE,
53                sub_class: Subclass::BRIDGE_PCI_TO_PCI,
54                base_class: ClassCode::BRIDGE,
55                type0_sub_vendor_id: 0,
56                type0_sub_system_id: 0,
57            },
58            vec![Box::new(PciExpressCapability::new(
59                DevicePortType::UpstreamSwitchPort,
60                None,
61            ))],
62        );
63        Self { cfg_space }
64    }
65
66    /// Get a reference to the configuration space emulator.
67    pub fn cfg_space(&self) -> &ConfigSpaceType1Emulator {
68        &self.cfg_space
69    }
70
71    /// Get a mutable reference to the configuration space emulator.
72    pub fn cfg_space_mut(&mut self) -> &mut ConfigSpaceType1Emulator {
73        &mut self.cfg_space
74    }
75}
76
77/// A PCI Express downstream switch port emulator.
78///
79/// A downstream switch port connects a switch to its children (e.g., endpoints or other switches).
80/// It appears as a Type 1 PCI-to-PCI bridge with PCIe capability indicating it's a downstream switch port.
81#[derive(Inspect)]
82pub struct DownstreamSwitchPort {
83    /// The common PCIe port implementation.
84    #[inspect(flatten)]
85    port: PcieDownstreamPort,
86}
87
88impl DownstreamSwitchPort {
89    /// Constructs a new [`DownstreamSwitchPort`] emulator.
90    ///
91    /// # Arguments
92    /// * `name` - The name for this downstream switch port
93    /// * `multi_function` - Whether this port should have the multi-function flag set (default: false)
94    /// * `hotplug_slot_number` - The slot number for hotplug support. `Some(slot_number)` enables hotplug, `None` disables it
95    pub fn new(
96        name: impl Into<Arc<str>>,
97        multi_function: Option<bool>,
98        hotplug_slot_number: Option<u32>,
99    ) -> Self {
100        let multi_function = multi_function.unwrap_or(false);
101        let hardware_ids = HardwareIds {
102            vendor_id: VENDOR_ID,
103            device_id: DOWNSTREAM_SWITCH_PORT_DEVICE_ID,
104            revision_id: 0,
105            prog_if: ProgrammingInterface::NONE,
106            sub_class: Subclass::BRIDGE_PCI_TO_PCI,
107            base_class: ClassCode::BRIDGE,
108            type0_sub_vendor_id: 0,
109            type0_sub_system_id: 0,
110        };
111
112        let port = PcieDownstreamPort::new(
113            name.into().to_string(),
114            hardware_ids,
115            DevicePortType::DownstreamSwitchPort,
116            multi_function,
117            hotplug_slot_number,
118        );
119
120        Self { port }
121    }
122
123    /// Get a reference to the configuration space emulator.
124    pub fn cfg_space(&self) -> &ConfigSpaceType1Emulator {
125        &self.port.cfg_space
126    }
127
128    /// Get a mutable reference to the configuration space emulator.
129    pub fn cfg_space_mut(&mut self) -> &mut ConfigSpaceType1Emulator {
130        &mut self.port.cfg_space
131    }
132}
133
134/// A PCI Express switch definition used for creating switch instances.
135pub struct GenericPcieSwitchDefinition {
136    /// The name of the switch.
137    pub name: Arc<str>,
138    /// The number of downstream ports to create.
139    /// TODO: implement physical slot number, link and slot stuff
140    pub downstream_port_count: u8,
141    /// Whether hotplug is enabled for this switch's downstream ports.
142    pub hotplug: bool,
143}
144
145/// A PCI Express switch emulator that implements a complete switch with upstream and downstream ports.
146///
147/// A PCIe switch consists of:
148/// - One upstream switch port that connects to the parent (root port or another switch)
149/// - Multiple downstream switch ports that connect to children (endpoints or other switches)
150///
151/// The switch implements routing functionality to forward configuration space accesses
152/// between the upstream and downstream ports based on bus number assignments.
153#[derive(InspectMut)]
154pub struct GenericPcieSwitch {
155    /// The name of this switch instance.
156    name: Arc<str>,
157    /// The upstream switch port that connects to the parent.
158    upstream_port: UpstreamSwitchPort,
159    /// Map of downstream switch ports, indexed by port number.
160    #[inspect(with = "|x| inspect::iter_by_key(x).map_value(|(_, v)| v)")]
161    downstream_ports: HashMap<u8, (Arc<str>, DownstreamSwitchPort)>,
162}
163
164impl GenericPcieSwitch {
165    /// Constructs a new [`GenericPcieSwitch`] emulator.
166    pub fn new(definition: GenericPcieSwitchDefinition) -> Self {
167        let upstream_port = UpstreamSwitchPort::new();
168
169        // If there are multiple downstream ports, they need the multi-function flag set
170        let multi_function = definition.downstream_port_count > 1;
171
172        let downstream_ports = (0..definition.downstream_port_count)
173            .map(|i| {
174                let port_name = format!("{}-downstream-{}", definition.name, i);
175                // Use the port index as the slot number for hotpluggable ports
176                let hotplug_slot_number = if definition.hotplug {
177                    Some((i as u32) + 1)
178                } else {
179                    None
180                };
181                let port = DownstreamSwitchPort::new(
182                    port_name.clone(),
183                    Some(multi_function),
184                    hotplug_slot_number,
185                );
186                (i, (port_name.into(), port))
187            })
188            .collect();
189
190        Self {
191            name: definition.name,
192            upstream_port,
193            downstream_ports,
194        }
195    }
196
197    /// Get the name of this switch.
198    pub fn name(&self) -> &Arc<str> {
199        &self.name
200    }
201
202    /// Get a reference to the upstream switch port.
203    pub fn upstream_port(&self) -> &UpstreamSwitchPort {
204        &self.upstream_port
205    }
206
207    /// Enumerate the downstream ports of the switch.
208    pub fn downstream_ports(&self) -> Vec<(u8, Arc<str>)> {
209        self.downstream_ports
210            .iter()
211            .map(|(port, (name, _))| (*port, name.clone()))
212            .collect()
213    }
214
215    /// Route configuration space read to the appropriate port based on addressing.
216    fn route_cfg_read(
217        &mut self,
218        bus: u8,
219        device_function: u8,
220        cfg_offset: u16,
221        value: &mut u32,
222    ) -> Option<IoResult> {
223        let upstream_bus_range = self.upstream_port.cfg_space().assigned_bus_range();
224
225        // If the bus range is 0..=0, this indicates invalid/uninitialized bus configuration
226        if upstream_bus_range == (0..=0) {
227            return None;
228        }
229
230        // Only handle accesses within our decoded bus range
231        if !upstream_bus_range.contains(&bus) {
232            return None;
233        }
234
235        let secondary_bus = *upstream_bus_range.start();
236
237        // Direct access to downstream switch ports on the secondary bus
238        if bus == secondary_bus {
239            return self.handle_downstream_port_read(device_function, cfg_offset, value);
240        }
241
242        // Route to downstream ports for further forwarding
243        self.route_read_to_downstream_ports(bus, device_function, cfg_offset, value)
244    }
245
246    /// Route configuration space write to the appropriate port based on addressing.
247    fn route_cfg_write(
248        &mut self,
249        bus: u8,
250        device_function: u8,
251        cfg_offset: u16,
252        value: u32,
253    ) -> Option<IoResult> {
254        let upstream_bus_range = self.upstream_port.cfg_space().assigned_bus_range();
255
256        // If the bus range is 0..=0, this indicates invalid/uninitialized bus configuration
257        if upstream_bus_range == (0..=0) {
258            return None;
259        }
260
261        // Only handle accesses within our decoded bus range
262        if !upstream_bus_range.contains(&bus) {
263            return None;
264        }
265
266        let secondary_bus = *upstream_bus_range.start();
267
268        // Direct access to downstream switch ports on the secondary bus
269        if bus == secondary_bus {
270            return self.handle_downstream_port_write(device_function, cfg_offset, value);
271        }
272
273        // Route to downstream ports for further forwarding
274        self.route_write_to_downstream_ports(bus, device_function, cfg_offset, value)
275    }
276
277    /// Handle direct configuration space read to downstream switch ports.
278    fn handle_downstream_port_read(
279        &mut self,
280        device_function: u8,
281        cfg_offset: u16,
282        value: &mut u32,
283    ) -> Option<IoResult> {
284        if let Some((_, downstream_port)) = self.downstream_ports.get_mut(&device_function) {
285            Some(downstream_port.port.cfg_space.read_u32(cfg_offset, value))
286        } else {
287            // No downstream switch port found for this device function
288            None
289        }
290    }
291
292    /// Handle direct configuration space write to downstream switch ports.
293    fn handle_downstream_port_write(
294        &mut self,
295        device_function: u8,
296        cfg_offset: u16,
297        value: u32,
298    ) -> Option<IoResult> {
299        if let Some((_, downstream_port)) = self.downstream_ports.get_mut(&device_function) {
300            Some(downstream_port.port.cfg_space.write_u32(cfg_offset, value))
301        } else {
302            // No downstream switch port found for this device function
303            None
304        }
305    }
306
307    /// Route configuration space read to downstream ports for further forwarding.
308    fn route_read_to_downstream_ports(
309        &mut self,
310        bus: u8,
311        device_function: u8,
312        cfg_offset: u16,
313        value: &mut u32,
314    ) -> Option<IoResult> {
315        for (_, downstream_port) in self.downstream_ports.values_mut() {
316            let downstream_bus_range = downstream_port.cfg_space().assigned_bus_range();
317
318            // Skip downstream ports with invalid/uninitialized bus configuration
319            if downstream_bus_range == (0..=0) {
320                continue;
321            }
322
323            if downstream_bus_range.contains(&bus) {
324                return Some(downstream_port.port.forward_cfg_read_with_routing(
325                    &bus,
326                    &device_function,
327                    cfg_offset,
328                    value,
329                ));
330            }
331        }
332
333        // No downstream port could handle this bus number
334        None
335    }
336
337    /// Route configuration space write to downstream ports for further forwarding.
338    fn route_write_to_downstream_ports(
339        &mut self,
340        bus: u8,
341        device_function: u8,
342        cfg_offset: u16,
343        value: u32,
344    ) -> Option<IoResult> {
345        for (_, downstream_port) in self.downstream_ports.values_mut() {
346            let downstream_bus_range = downstream_port.cfg_space().assigned_bus_range();
347
348            // Skip downstream ports with invalid/uninitialized bus configuration
349            if downstream_bus_range == (0..=0) {
350                continue;
351            }
352
353            if downstream_bus_range.contains(&bus) {
354                return Some(downstream_port.port.forward_cfg_write_with_routing(
355                    &bus,
356                    &device_function,
357                    cfg_offset,
358                    value,
359                ));
360            }
361        }
362
363        // No downstream port could handle this bus number
364        None
365    }
366
367    /// Attach the provided `GenericPciBusDevice` to the port identified.
368    pub fn add_pcie_device(
369        &mut self,
370        port: u8,
371        name: &str,
372        dev: Box<dyn GenericPciBusDevice>,
373    ) -> anyhow::Result<()> {
374        // Find the specific downstream port that matches the port number
375        if let Some((port_name, downstream_port)) = self.downstream_ports.get_mut(&port) {
376            // Found the matching port, try to connect to it using the port's name
377            downstream_port
378                .port
379                .add_pcie_device(port_name.as_ref(), name, dev)
380                .context("failed to add PCIe device to downstream port")?;
381            Ok(())
382        } else {
383            // No downstream port found with matching port number
384            bail!("port {} not found", port);
385        }
386    }
387}
388
389impl ChangeDeviceState for GenericPcieSwitch {
390    fn start(&mut self) {}
391
392    async fn stop(&mut self) {}
393
394    async fn reset(&mut self) {
395        // Reset the upstream port configuration space
396        self.upstream_port.cfg_space.reset();
397
398        // Reset all downstream port configuration spaces
399        for (_, downstream_port) in self.downstream_ports.values_mut() {
400            downstream_port.port.cfg_space.reset();
401        }
402    }
403}
404
405impl ChipsetDevice for GenericPcieSwitch {
406    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
407        Some(self)
408    }
409}
410
411impl PciConfigSpace for GenericPcieSwitch {
412    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
413        // Forward to the upstream port's configuration space (the switch presents as the upstream port)
414        self.upstream_port.cfg_space.read_u32(offset, value)
415    }
416
417    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
418        // Forward to the upstream port's configuration space (the switch presents as the upstream port)
419        self.upstream_port.cfg_space.write_u32(offset, value)
420    }
421
422    fn pci_cfg_read_forward(
423        &mut self,
424        bus: u8,
425        device_function: u8,
426        offset: u16,
427        value: &mut u32,
428    ) -> Option<IoResult> {
429        self.route_cfg_read(bus, device_function, offset, value)
430    }
431
432    fn pci_cfg_write_forward(
433        &mut self,
434        bus: u8,
435        device_function: u8,
436        offset: u16,
437        value: u32,
438    ) -> Option<IoResult> {
439        self.route_cfg_write(bus, device_function, offset, value)
440    }
441
442    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
443        // PCIe switches typically don't have a fixed BDF requirement
444        None
445    }
446}
447
448mod save_restore {
449    use super::*;
450    use vmcore::save_restore::SaveError;
451    use vmcore::save_restore::SaveRestore;
452    use vmcore::save_restore::SavedStateNotSupported;
453
454    impl SaveRestore for GenericPcieSwitch {
455        type SavedState = SavedStateNotSupported;
456
457        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
458            Err(SaveError::NotSupported)
459        }
460
461        fn restore(
462            &mut self,
463            state: Self::SavedState,
464        ) -> Result<(), vmcore::save_restore::RestoreError> {
465            match state {}
466        }
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    #[test]
475    fn test_upstream_switch_port_creation() {
476        let port = UpstreamSwitchPort::new();
477
478        // Verify that we can read the vendor/device ID from config space
479        let mut vendor_device_id: u32 = 0;
480        port.cfg_space.read_u32(0x0, &mut vendor_device_id).unwrap();
481        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
482        assert_eq!(vendor_device_id, expected);
483    }
484
485    #[test]
486    fn test_downstream_switch_port_creation() {
487        let port = DownstreamSwitchPort::new("test-downstream-port", None, None);
488        assert!(port.port.link.is_none());
489
490        // Verify that we can read the vendor/device ID from config space
491        let mut vendor_device_id: u32 = 0;
492        port.port
493            .cfg_space
494            .read_u32(0x0, &mut vendor_device_id)
495            .unwrap();
496        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
497        assert_eq!(vendor_device_id, expected);
498    }
499
500    #[test]
501    fn test_downstream_switch_port_multi_function_options() {
502        // Test with default multi_function (false)
503        let port_default = DownstreamSwitchPort::new("test-port-default", None, None);
504        let mut header_type_value: u32 = 0;
505        port_default
506            .cfg_space()
507            .read_u32(0x0C, &mut header_type_value)
508            .unwrap();
509        let header_type_field = (header_type_value >> 16) & 0xFF;
510        assert_eq!(
511            header_type_field & 0x80,
512            0x00,
513            "Multi-function bit should NOT be set with None parameter"
514        );
515
516        // Test with explicit multi_function false
517        let port_false = DownstreamSwitchPort::new("test-port-false", Some(false), None);
518        let mut header_type_value_false: u32 = 0;
519        port_false
520            .cfg_space()
521            .read_u32(0x0C, &mut header_type_value_false)
522            .unwrap();
523        let header_type_field_false = (header_type_value_false >> 16) & 0xFF;
524        assert_eq!(
525            header_type_field_false & 0x80,
526            0x00,
527            "Multi-function bit should NOT be set with Some(false)"
528        );
529
530        // Test with explicit multi_function true
531        let port_true = DownstreamSwitchPort::new("test-port-true", Some(true), None);
532        let mut header_type_value_true: u32 = 0;
533        port_true
534            .cfg_space()
535            .read_u32(0x0C, &mut header_type_value_true)
536            .unwrap();
537        let header_type_field_true = (header_type_value_true >> 16) & 0xFF;
538        assert_eq!(
539            header_type_field_true & 0x80,
540            0x80,
541            "Multi-function bit should be set with Some(true)"
542        );
543    }
544
545    #[test]
546    fn test_downstream_switch_port_hotplug_options() {
547        // Test with hotplug disabled (None)
548        let port_no_hotplug = DownstreamSwitchPort::new("test-port-no-hotplug", None, None);
549        // We can't easily verify hotplug is disabled without accessing internal state,
550        // but we can verify the port was created successfully
551        let mut vendor_device_id: u32 = 0;
552        port_no_hotplug
553            .cfg_space()
554            .read_u32(0x0, &mut vendor_device_id)
555            .unwrap();
556        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
557        assert_eq!(vendor_device_id, expected);
558
559        // Test with hotplug enabled (Some(slot_number))
560        let port_with_hotplug = DownstreamSwitchPort::new("test-port-hotplug", None, Some(42));
561        let mut vendor_device_id_hotplug: u32 = 0;
562        port_with_hotplug
563            .cfg_space()
564            .read_u32(0x0, &mut vendor_device_id_hotplug)
565            .unwrap();
566        assert_eq!(vendor_device_id_hotplug, expected);
567        // The slot number and hotplug capability would be tested via PCIe capability registers
568        // but that requires more complex setup
569    }
570
571    #[test]
572    fn test_switch_creation() {
573        let definition = GenericPcieSwitchDefinition {
574            name: "test-switch".into(),
575            downstream_port_count: 3,
576            hotplug: false,
577        };
578        let switch = GenericPcieSwitch::new(definition);
579
580        assert_eq!(switch.name().as_ref(), "test-switch");
581        assert_eq!(switch.downstream_ports().len(), 3);
582
583        // Verify downstream port names (HashMap doesn't guarantee order, so check each one exists)
584        let ports = switch.downstream_ports();
585        let port_names: std::collections::HashSet<_> =
586            ports.iter().map(|(_, name)| name.as_ref()).collect();
587        assert!(port_names.contains("test-switch-downstream-0"));
588        assert!(port_names.contains("test-switch-downstream-1"));
589        assert!(port_names.contains("test-switch-downstream-2"));
590
591        // Verify port numbers
592        let port_numbers: std::collections::HashSet<_> =
593            ports.iter().map(|(num, _)| *num).collect();
594        assert!(port_numbers.contains(&0));
595        assert!(port_numbers.contains(&1));
596        assert!(port_numbers.contains(&2));
597    }
598
599    #[test]
600    fn test_switch_device_connections() {
601        use crate::test_helpers::TestPcieEndpoint;
602        use chipset_device::io::IoError;
603
604        let definition = GenericPcieSwitchDefinition {
605            name: "test-switch".into(),
606            downstream_port_count: 2,
607            hotplug: false,
608        };
609        let mut switch = GenericPcieSwitch::new(definition);
610
611        let downstream_device = TestPcieEndpoint::new(
612            |offset, value| match offset {
613                0x0 => {
614                    *value = 0xABCD_EF01;
615                    Some(IoResult::Ok)
616                }
617                _ => Some(IoResult::Err(IoError::InvalidRegister)),
618            },
619            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
620        );
621
622        // Connect downstream device to port 0
623        assert!(
624            switch
625                .add_pcie_device(
626                    0, // Port number instead of port name
627                    "downstream-dev",
628                    Box::new(downstream_device)
629                )
630                .is_ok()
631        );
632
633        // Try to connect to invalid port
634        let invalid_device = TestPcieEndpoint::new(
635            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
636            |_, _| Some(IoResult::Err(IoError::InvalidRegister)),
637        );
638        let result = switch.add_pcie_device(99, "invalid-dev", Box::new(invalid_device)); // Use invalid port number
639        assert!(result.is_err());
640        // add_pcie_device returns an anyhow::Error on failure,
641        // so we just verify that the connection failed
642        assert!(result.is_err());
643    }
644
645    #[test]
646    fn test_switch_routing_functionality() {
647        use crate::test_helpers::TestPcieEndpoint;
648        use chipset_device::io::IoResult;
649
650        let definition = GenericPcieSwitchDefinition {
651            name: "test-switch".into(),
652            downstream_port_count: 2,
653            hotplug: false,
654        };
655        let mut switch = GenericPcieSwitch::new(definition);
656
657        // Verify that Switch implements routing functionality by testing add_pcie_device method
658        // This tests that the switch can accept device connections (routing capability)
659        let test_device =
660            TestPcieEndpoint::new(|_, _| Some(IoResult::Ok), |_, _| Some(IoResult::Ok));
661        let add_result = switch.add_pcie_device(0, "test-device", Box::new(test_device));
662        // Should succeed for port 0 (first downstream port)
663        assert!(add_result.is_ok());
664
665        // Test basic configuration space access through the PCI interface
666        let mut value = 0u32;
667        let result = switch
668            .upstream_port
669            .cfg_space_mut()
670            .read_u32(0x0, &mut value);
671        assert!(matches!(result, IoResult::Ok));
672
673        // Verify vendor/device ID is from the upstream port
674        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
675        assert_eq!(value, expected);
676    }
677
678    #[test]
679    fn test_switch_chipset_device() {
680        use chipset_device::ChipsetDevice;
681        use chipset_device::pci::PciConfigSpace;
682
683        let definition = GenericPcieSwitchDefinition {
684            name: "test-switch".into(),
685            downstream_port_count: 4,
686            hotplug: false,
687        };
688        let mut switch = GenericPcieSwitch::new(definition);
689
690        // Test that it supports PCI but not other interfaces
691        assert!(switch.supports_pci().is_some());
692        assert!(switch.supports_mmio().is_none());
693        assert!(switch.supports_pio().is_none());
694        assert!(switch.supports_poll_device().is_none());
695
696        // Test PciConfigSpace interface
697        let mut value = 0u32;
698        let result = PciConfigSpace::pci_cfg_read(&mut switch, 0x0, &mut value);
699        assert!(matches!(result, IoResult::Ok));
700
701        // Verify we get the expected vendor/device ID
702        let expected = (UPSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
703        assert_eq!(value, expected);
704
705        // Test write operation
706        let result = PciConfigSpace::pci_cfg_write(&mut switch, 0x4, 0x12345678);
707        assert!(matches!(result, IoResult::Ok));
708    }
709
710    #[test]
711    fn test_switch_default() {
712        let definition = GenericPcieSwitchDefinition {
713            name: "default-switch".into(),
714            downstream_port_count: 4,
715            hotplug: false,
716        };
717        let switch = GenericPcieSwitch::new(definition);
718        assert_eq!(switch.name().as_ref(), "default-switch");
719        assert_eq!(switch.downstream_ports().len(), 4);
720    }
721
722    #[test]
723    fn test_switch_large_downstream_port_count() {
724        let definition = GenericPcieSwitchDefinition {
725            name: "test-switch".into(),
726            downstream_port_count: 16,
727            hotplug: false,
728        };
729        let switch = GenericPcieSwitch::new(definition);
730        assert_eq!(switch.downstream_ports().len(), 16);
731    }
732
733    #[test]
734    fn test_switch_downstream_port_direct_access() {
735        let definition = GenericPcieSwitchDefinition {
736            name: "test-switch".into(),
737            downstream_port_count: 3,
738            hotplug: false,
739        };
740        let mut switch = GenericPcieSwitch::new(definition);
741
742        // Simulate the switch's internal bus being assigned as bus 1
743        let secondary_bus = 1u8;
744        // Set secondary bus number (offset 0x18) - bits 8-15 of the 32-bit value at 0x18
745        let bus_config = (10u32 << 24) | ((secondary_bus as u32) << 16); // subordinate | secondary
746        switch
747            .upstream_port
748            .cfg_space_mut()
749            .write_u32(0x18, bus_config)
750            .unwrap();
751
752        let bus_range = switch.upstream_port.cfg_space().assigned_bus_range();
753        let switch_internal_bus = *bus_range.start(); // This is the secondary bus
754
755        // Test direct access to downstream port 0 using device_function = 0
756        let mut value = 0u32;
757        let result = switch.route_cfg_read(switch_internal_bus, 0, 0x0, &mut value);
758        assert!(result.is_some());
759
760        // Verify we got the downstream switch port's vendor/device ID
761        let expected = (DOWNSTREAM_SWITCH_PORT_DEVICE_ID as u32) << 16 | (VENDOR_ID as u32);
762        assert_eq!(value, expected);
763
764        // Test direct access to downstream port 2 using device_function = 2
765        let mut value2 = 0u32;
766        let result2 = switch.route_cfg_read(switch_internal_bus, 2, 0x0, &mut value2);
767        assert!(result2.is_some());
768        assert_eq!(value2, expected);
769
770        // Test access to non-existent downstream port using device_function = 5
771        let mut value3 = 0u32;
772        let result3 = switch.route_cfg_read(switch_internal_bus, 5, 0x0, &mut value3);
773        assert!(result3.is_none());
774    }
775
776    #[test]
777    fn test_switch_invalid_bus_range_handling() {
778        let definition = GenericPcieSwitchDefinition {
779            name: "test-switch".into(),
780            downstream_port_count: 2,
781            hotplug: false,
782        };
783        let mut switch = GenericPcieSwitch::new(definition);
784
785        // Don't configure bus numbers, so the range should be 0..=0 (invalid)
786        let bus_range = switch.upstream_port.cfg_space().assigned_bus_range();
787        assert_eq!(bus_range, 0..=0);
788
789        // Test that any access returns None when bus range is invalid
790        let mut value = 0u32;
791        let result = switch.route_cfg_read(0, 0, 0x0, &mut value);
792        assert!(result.is_none());
793
794        let result2 = switch.route_cfg_read(1, 0, 0x0, &mut value);
795        assert!(result2.is_none());
796
797        let result3 = switch.route_cfg_write(0, 0, 0x0, value);
798        assert!(result3.is_none());
799    }
800
801    #[test]
802    fn test_switch_downstream_port_invalid_bus_range_skipping() {
803        let definition = GenericPcieSwitchDefinition {
804            name: "test-switch".into(),
805            downstream_port_count: 2,
806            hotplug: false,
807        };
808        let mut switch = GenericPcieSwitch::new(definition);
809
810        // Configure the upstream port with a valid bus range
811        let secondary_bus = 1u8;
812        let subordinate_bus = 10u8;
813        let primary_bus = 0u8;
814        let bus_config =
815            ((subordinate_bus as u32) << 16) | ((secondary_bus as u32) << 8) | (primary_bus as u32); // subordinate | secondary | primary
816        switch
817            .upstream_port
818            .cfg_space_mut()
819            .write_u32(0x18, bus_config)
820            .unwrap();
821
822        // Downstream ports still have invalid bus ranges (0..=0 by default)
823        // so any access to buses beyond the secondary bus should return None
824        let mut value = 0u32;
825
826        // Access to bus 2 should return None since no downstream port has a valid bus range
827        let result = switch.route_cfg_read(2, 0, 0x0, &mut value);
828        assert!(result.is_none());
829
830        // Access to bus 5 should also return None
831        let result2 = switch.route_cfg_read(5, 0, 0x0, &mut value);
832        assert!(result2.is_none());
833
834        // Access to the secondary bus (switch internal) should still work for downstream port config
835        let result3 = switch.route_cfg_read(secondary_bus, 0, 0x0, &mut value);
836        assert!(result3.is_some());
837    }
838
839    #[test]
840    fn test_switch_multi_function_bit() {
841        // Test that switches with multiple downstream ports set the multi-function bit
842        let multi_port_definition = GenericPcieSwitchDefinition {
843            name: "multi-port-switch".into(),
844            downstream_port_count: 3,
845            hotplug: false,
846        };
847        let multi_port_switch = GenericPcieSwitch::new(multi_port_definition);
848
849        // Verify each downstream port has the multi-function bit set
850        for (port_num, _) in multi_port_switch.downstream_ports() {
851            if let Some((_, downstream_port)) = multi_port_switch.downstream_ports.get(&port_num) {
852                let mut header_type_value: u32 = 0;
853                downstream_port
854                    .cfg_space()
855                    .read_u32(0x0C, &mut header_type_value)
856                    .unwrap();
857
858                // Extract the header type field (bits 16-23, with multi-function bit at bit 23)
859                let header_type_field = (header_type_value >> 16) & 0xFF;
860
861                // Multi-function bit should be set (bit 7 of header type field = bit 23 of dword)
862                assert_eq!(
863                    header_type_field & 0x80,
864                    0x80,
865                    "Multi-function bit should be set for downstream port {} in multi-port switch",
866                    port_num
867                );
868
869                // Base header type should still be 01 (bridge)
870                assert_eq!(
871                    header_type_field & 0x7F,
872                    0x01,
873                    "Header type should be 01 (bridge) for downstream port {}",
874                    port_num
875                );
876            }
877        }
878
879        // Test that switches with single downstream port do NOT set the multi-function bit
880        let single_port_definition = GenericPcieSwitchDefinition {
881            name: "single-port-switch".into(),
882            downstream_port_count: 1,
883            hotplug: false,
884        };
885        let single_port_switch = GenericPcieSwitch::new(single_port_definition);
886
887        // Verify the single downstream port does NOT have the multi-function bit set
888        for (port_num, _) in single_port_switch.downstream_ports() {
889            if let Some((_, downstream_port)) = single_port_switch.downstream_ports.get(&port_num) {
890                let mut header_type_value: u32 = 0;
891                downstream_port
892                    .cfg_space()
893                    .read_u32(0x0C, &mut header_type_value)
894                    .unwrap();
895
896                // Extract the header type field (bits 16-23)
897                let header_type_field = (header_type_value >> 16) & 0xFF;
898
899                // Multi-function bit should NOT be set
900                assert_eq!(
901                    header_type_field & 0x80,
902                    0x00,
903                    "Multi-function bit should NOT be set for downstream port {} in single-port switch",
904                    port_num
905                );
906
907                // Base header type should still be 01 (bridge)
908                assert_eq!(
909                    header_type_field & 0x7F,
910                    0x01,
911                    "Header type should be 01 (bridge) for downstream port {}",
912                    port_num
913                );
914            }
915        }
916    }
917
918    #[test]
919    fn test_hotplug_support() {
920        // Test hotplug disabled
921        let definition_no_hotplug = GenericPcieSwitchDefinition {
922            name: "test-switch-no-hotplug".into(),
923            downstream_port_count: 1,
924            hotplug: false,
925        };
926        let switch_no_hotplug = GenericPcieSwitch::new(definition_no_hotplug);
927        assert_eq!(switch_no_hotplug.name().as_ref(), "test-switch-no-hotplug");
928
929        // Test hotplug enabled
930        let definition_with_hotplug = GenericPcieSwitchDefinition {
931            name: "test-switch-with-hotplug".into(),
932            downstream_port_count: 1,
933            hotplug: true,
934        };
935        let switch_with_hotplug = GenericPcieSwitch::new(definition_with_hotplug);
936        assert_eq!(
937            switch_with_hotplug.name().as_ref(),
938            "test-switch-with-hotplug"
939        );
940    }
941}