devices/pci/pcie/
pcie_switch.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::collections::BTreeMap;
6use std::str::FromStr;
7
8use anyhow::bail;
9use base::error;
10use base::Event;
11
12use crate::bus::HotPlugBus;
13use crate::bus::HotPlugKey;
14use crate::pci::pcie::pcie_port::PciePort;
15use crate::pci::pcie::pcie_port::PciePortVariant;
16use crate::pci::pcie::*;
17use crate::pci::PciAddress;
18use crate::pci::PciDeviceError;
19
20const PCIE_UP_DID: u16 = 0x3500;
21const PCIE_DP_DID: u16 = 0x3510;
22
23pub struct PcieUpstreamPort {
24    pcie_port: PciePort,
25    hotplugged: bool,
26    downstream_devices: BTreeMap<PciAddress, HotPlugKey>,
27}
28
29impl PcieUpstreamPort {
30    /// Constructs a new PCIE upstream port
31    pub fn new(primary_bus_num: u8, secondary_bus_num: u8, hotplugged: bool) -> Self {
32        PcieUpstreamPort {
33            pcie_port: PciePort::new(
34                PCIE_UP_DID,
35                "PcieUpstreamPort".to_string(),
36                primary_bus_num,
37                secondary_bus_num,
38                false,
39                PcieDevicePortType::UpstreamPort,
40            ),
41            hotplugged,
42            downstream_devices: BTreeMap::new(),
43        }
44    }
45
46    pub fn new_from_host(
47        pcie_host: PcieHostPort,
48        hotplugged: bool,
49    ) -> std::result::Result<Self, PciDeviceError> {
50        let pcie_port =
51            PciePort::new_from_host(pcie_host, false, PcieDevicePortType::UpstreamPort)?;
52        Ok(PcieUpstreamPort {
53            pcie_port,
54            hotplugged,
55            downstream_devices: BTreeMap::new(),
56        })
57    }
58}
59
60impl PciePortVariant for PcieUpstreamPort {
61    fn get_pcie_port(&self) -> &PciePort {
62        &self.pcie_port
63    }
64
65    fn get_pcie_port_mut(&mut self) -> &mut PciePort {
66        &mut self.pcie_port
67    }
68
69    fn get_removed_devices_impl(&self) -> Vec<PciAddress> {
70        Vec::new()
71    }
72
73    fn hotplug_implemented_impl(&self) -> bool {
74        false
75    }
76
77    fn hotplugged_impl(&self) -> bool {
78        self.hotplugged
79    }
80}
81
82// Even if upstream port do not have a slot present, we still implement hotplug
83// bus trait for it. Our purpose is simple. We want to store information of
84// downstream devices in this upstream port, so that they could be used during
85// hotplug out.
86impl HotPlugBus for PcieUpstreamPort {
87    // Do nothing. We are not a real hotplug bus.
88    fn hot_plug(&mut self, _addr: PciAddress) -> anyhow::Result<Option<Event>> {
89        bail!("hot plug not supported on upstream port.")
90    }
91
92    // Just remove the downstream device.
93    fn hot_unplug(&mut self, addr: PciAddress) -> anyhow::Result<Option<Event>> {
94        self.downstream_devices.remove(&addr);
95        Ok(None)
96    }
97
98    fn get_ready_notification(&mut self) -> anyhow::Result<Event> {
99        bail!("hot plug not supported on upstream port.")
100    }
101
102    fn get_secondary_bus_number(&self) -> Option<u8> {
103        Some(self.pcie_port.get_bus_range()?.secondary)
104    }
105
106    fn is_match(&self, host_addr: PciAddress) -> Option<u8> {
107        self.pcie_port.is_match(host_addr)
108    }
109
110    fn add_hotplug_device(&mut self, hotplug_key: HotPlugKey, guest_addr: PciAddress) {
111        self.downstream_devices.insert(guest_addr, hotplug_key);
112    }
113
114    fn get_address(&self) -> Option<PciAddress> {
115        self.pcie_port.get_address()
116    }
117
118    fn get_hotplug_device(&self, hotplug_key: HotPlugKey) -> Option<PciAddress> {
119        for (guest_address, host_info) in self.downstream_devices.iter() {
120            if hotplug_key == *host_info {
121                return Some(*guest_address);
122            }
123        }
124        None
125    }
126
127    fn is_empty(&self) -> bool {
128        self.downstream_devices.is_empty()
129    }
130
131    fn get_hotplug_key(&self) -> Option<HotPlugKey> {
132        if self.pcie_port.is_host() {
133            match PciAddress::from_str(&self.pcie_port.debug_label()) {
134                Ok(host_addr) => Some(HotPlugKey::HostUpstreamPort { host_addr }),
135                Err(e) => {
136                    error!(
137                        "failed to get hotplug key for {}: {}",
138                        self.pcie_port.debug_label(),
139                        e
140                    );
141                    None
142                }
143            }
144        } else {
145            None
146        }
147    }
148}
149
150pub struct PcieDownstreamPort {
151    pcie_port: PciePort,
152    hotplugged: bool,
153    downstream_devices: BTreeMap<PciAddress, HotPlugKey>,
154    hotplug_out_begin: bool,
155    removed_downstream: Vec<PciAddress>,
156}
157
158impl PcieDownstreamPort {
159    /// Constructs a new PCIE downstream port
160    pub fn new(primary_bus_num: u8, secondary_bus_num: u8, hotplugged: bool) -> Self {
161        PcieDownstreamPort {
162            pcie_port: PciePort::new(
163                PCIE_DP_DID,
164                "PcieDownstreamPort".to_string(),
165                primary_bus_num,
166                secondary_bus_num,
167                false,
168                PcieDevicePortType::DownstreamPort,
169            ),
170            hotplugged,
171            downstream_devices: BTreeMap::new(),
172            hotplug_out_begin: false,
173            removed_downstream: Vec::new(),
174        }
175    }
176
177    pub fn new_from_host(
178        pcie_host: PcieHostPort,
179        hotplugged: bool,
180    ) -> std::result::Result<Self, PciDeviceError> {
181        let pcie_port =
182            PciePort::new_from_host(pcie_host, true, PcieDevicePortType::DownstreamPort)?;
183        Ok(PcieDownstreamPort {
184            pcie_port,
185            hotplugged,
186            downstream_devices: BTreeMap::new(),
187            hotplug_out_begin: false,
188            removed_downstream: Vec::new(),
189        })
190    }
191}
192
193impl PciePortVariant for PcieDownstreamPort {
194    fn get_pcie_port(&self) -> &PciePort {
195        &self.pcie_port
196    }
197
198    fn get_pcie_port_mut(&mut self) -> &mut PciePort {
199        &mut self.pcie_port
200    }
201
202    fn get_removed_devices_impl(&self) -> Vec<PciAddress> {
203        if self.pcie_port.removed_downstream_valid() {
204            self.removed_downstream.clone()
205        } else {
206            Vec::new()
207        }
208    }
209
210    fn hotplug_implemented_impl(&self) -> bool {
211        false
212    }
213
214    fn hotplugged_impl(&self) -> bool {
215        self.hotplugged
216    }
217}
218
219impl HotPlugBus for PcieDownstreamPort {
220    fn hot_plug(&mut self, addr: PciAddress) -> anyhow::Result<Option<Event>> {
221        if !self.pcie_port.hotplug_implemented() {
222            bail!("hotplug not implemented.");
223        }
224        if !self.downstream_devices.contains_key(&addr) {
225            bail!("no downstream devices.");
226        }
227        if !self.pcie_port.is_hotplug_ready() {
228            bail!("Hot unplug fail: slot is not enabled by the guest yet.");
229        }
230        self.pcie_port
231            .set_slot_status(PCIE_SLTSTA_PDS | PCIE_SLTSTA_ABP);
232        self.pcie_port.trigger_hp_or_pme_interrupt();
233        Ok(None)
234    }
235
236    fn hot_unplug(&mut self, addr: PciAddress) -> anyhow::Result<Option<Event>> {
237        if !self.pcie_port.hotplug_implemented() {
238            bail!("hotplug not implemented.");
239        }
240        if self.downstream_devices.remove(&addr).is_none() {
241            bail!("no downstream devices.");
242        }
243        if !self.pcie_port.is_hotplug_ready() {
244            bail!("Hot unplug fail: slot is not enabled by the guest yet.");
245        }
246
247        if !self.hotplug_out_begin {
248            self.removed_downstream.clear();
249            self.removed_downstream.push(addr);
250            // All the remaine devices will be removed also in this hotplug out interrupt
251            for (guest_pci_addr, _) in self.downstream_devices.iter() {
252                self.removed_downstream.push(*guest_pci_addr);
253            }
254            self.pcie_port.set_slot_status(PCIE_SLTSTA_ABP);
255            self.pcie_port.trigger_hp_or_pme_interrupt();
256            let slot_control = self.pcie_port.get_slot_control();
257            match slot_control & PCIE_SLTCTL_PIC {
258                PCIE_SLTCTL_PIC_ON => {
259                    self.pcie_port.set_slot_status(PCIE_SLTSTA_ABP);
260                    self.pcie_port.trigger_hp_or_pme_interrupt();
261                }
262                PCIE_SLTCTL_PIC_OFF => {
263                    // Do not press attention button, as the slot is already off. Likely caused by
264                    // previous hot plug failed.
265                    self.pcie_port.mask_slot_status(!PCIE_SLTSTA_PDS);
266                }
267                _ => {
268                    // Power indicator in blinking state.
269                    bail!("Hot unplug fail: Power indicator is blinking.");
270                }
271            }
272
273            if self.pcie_port.is_host() {
274                self.pcie_port.hot_unplug()
275            }
276        }
277
278        self.hotplug_out_begin = true;
279        Ok(None)
280    }
281
282    fn get_ready_notification(&mut self) -> anyhow::Result<Event> {
283        Ok(self.pcie_port.get_ready_notification()?)
284    }
285
286    fn get_address(&self) -> Option<PciAddress> {
287        self.pcie_port.get_address()
288    }
289
290    fn get_secondary_bus_number(&self) -> Option<u8> {
291        Some(self.pcie_port.get_bus_range()?.secondary)
292    }
293
294    fn is_match(&self, host_addr: PciAddress) -> Option<u8> {
295        self.pcie_port.is_match(host_addr)
296    }
297
298    fn add_hotplug_device(&mut self, hotplug_key: HotPlugKey, guest_addr: PciAddress) {
299        if self.hotplug_out_begin {
300            self.hotplug_out_begin = false;
301            self.downstream_devices.clear();
302            self.removed_downstream.clear();
303        }
304
305        self.downstream_devices.insert(guest_addr, hotplug_key);
306    }
307
308    fn get_hotplug_device(&self, hotplug_key: HotPlugKey) -> Option<PciAddress> {
309        for (guest_address, host_info) in self.downstream_devices.iter() {
310            if hotplug_key == *host_info {
311                return Some(*guest_address);
312            }
313        }
314        None
315    }
316
317    fn is_empty(&self) -> bool {
318        self.downstream_devices.is_empty()
319    }
320
321    fn get_hotplug_key(&self) -> Option<HotPlugKey> {
322        if self.pcie_port.is_host() {
323            match PciAddress::from_str(&self.pcie_port.debug_label()) {
324                Ok(host_addr) => Some(HotPlugKey::HostDownstreamPort { host_addr }),
325                Err(e) => {
326                    error!(
327                        "failed to get hotplug key for {}: {}",
328                        self.pcie_port.debug_label(),
329                        e
330                    );
331                    None
332                }
333            }
334        } else {
335            None
336        }
337    }
338}