• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 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 
5 //! A high-level manager for hotplug PCI devices.
6 
7 // TODO(b/243767476): Support aarch64.
8 use std::collections::BTreeMap;
9 use std::collections::HashMap;
10 use std::sync::mpsc;
11 use std::sync::Arc;
12 use std::thread;
13 use std::time::Duration;
14 
15 use anyhow::anyhow;
16 use anyhow::bail;
17 use anyhow::Context;
18 use anyhow::Error;
19 use arch::RunnableLinuxVm;
20 use arch::VcpuArch;
21 use arch::VmArch;
22 use devices::HotPlugBus;
23 use devices::HotPlugKey;
24 use devices::IrqEventSource;
25 use devices::IrqLevelEvent;
26 use devices::PciAddress;
27 use devices::PciInterruptPin;
28 use devices::PciRootCommand;
29 use devices::ResourceCarrier;
30 use log::debug;
31 use log::error;
32 use resources::SystemAllocator;
33 #[cfg(feature = "swap")]
34 use swap::SwapDeviceHelper;
35 use sync::Mutex;
36 use vm_memory::GuestMemory;
37 
38 use crate::crosvm::sys::linux::JailWarden;
39 use crate::crosvm::sys::linux::JailWardenImpl;
40 use crate::crosvm::sys::linux::PermissiveJailWarden;
41 use crate::Config;
42 
43 pub type Result<T> = std::result::Result<T, Error>;
44 
45 const PCI_SLOT_TIMEOUT: Duration = Duration::from_secs(1);
46 
47 /// PciHotPlugManager manages hotplug ports, and handles PCI device hot plug and hot removal.
48 pub struct PciHotPlugManager {
49     /// map of empty ports available.
50     ///
51     /// empty ports are sorted such to ensure the ordering of the hotplugged devices. Specifically,
52     /// if multiple devices are hotplugged before PCI enumeration, they will appear in the guest OS
53     /// in the same order hotplug_device is called.
54     port_pool: PortPool,
55     /// map of occupied ports, indexed by downstream bus number
56     occupied_ports: HashMap<u8, PortStub>,
57     /// JailWarden for jailing hotplug devices
58     jail_warden: Box<dyn JailWarden>,
59     /// control channel to root bus
60     rootbus_controller: Option<mpsc::Sender<PciRootCommand>>,
61 }
62 
63 /// HotPlugAvailability indicates when a port is available for hotplug.
64 #[derive(Debug)]
65 enum HotPlugAvailability {
66     /// available now
67     Now,
68     /// available after notification is received.
69     After(mpsc::Receiver<()>),
70 }
71 
72 /// PortStub contains a hotplug port and set of hotplug devices on it.
73 struct PortStub {
74     /// PCI address of the port (at upstream)
75     pci_address: PciAddress,
76     /// index of downstream bus
77     downstream_bus: u8,
78     /// hotplug port
79     port: Arc<Mutex<dyn HotPlugBus>>,
80     /// Map of hotplugged devices, and system resources that can be released when device is
81     /// removed.
82     devices: HashMap<PciAddress, RecoverableResource>,
83 }
84 
85 impl PortStub {
86     /// Sends hotplug signal on the port.
send_hotplug_signal(&mut self) -> Result<()>87     fn send_hotplug_signal(&mut self) -> Result<()> {
88         let base_pci_address = PciAddress::new(0, self.downstream_bus.into(), 0, 0)?;
89         self.port.lock().hot_plug(base_pci_address)?;
90         Ok(())
91     }
92 
93     /// Sends hotplug signal after notification on the port.
send_hotplug_signal_after_notification( &mut self, notf_recvr: mpsc::Receiver<()>, ) -> Result<()>94     fn send_hotplug_signal_after_notification(
95         &mut self,
96         notf_recvr: mpsc::Receiver<()>,
97     ) -> Result<()> {
98         let base_pci_address = PciAddress::new(0, self.downstream_bus.into(), 0, 0)?;
99         let weak_port = Arc::downgrade(&self.port);
100         thread::spawn(move || {
101             if let Err(e) = notf_recvr.recv_timeout(PCI_SLOT_TIMEOUT) {
102                 error!(
103                     "failed to receive hot unplug command complete notification: {:#}",
104                     &e
105                 );
106             }
107             match weak_port.upgrade() {
108                 Some(port) => {
109                     if let Err(e) = port.lock().hot_plug(base_pci_address) {
110                         error!("hot plug failed: {:#}", &e);
111                     }
112                 }
113                 None => {
114                     error!("hotplug signal cancelled since port is out of scope");
115                 }
116             }
117         });
118         Ok(())
119     }
120 }
121 
122 /// System resources that can be released when a hotplugged device is removed.
123 struct RecoverableResource {
124     irq_num: u32,
125     irq_evt: IrqLevelEvent,
126 }
127 
128 impl PciHotPlugManager {
129     /// Constructs PciHotPlugManager.
130     ///
131     /// Constructor uses forking, therefore has to be called early, before crosvm enters a
132     /// multi-threaded context.
new( guest_memory: GuestMemory, config: &Config, #[cfg(feature = "swap")] swap_device_helper: Option<SwapDeviceHelper>, ) -> Result<Self>133     pub fn new(
134         guest_memory: GuestMemory,
135         config: &Config,
136         #[cfg(feature = "swap")] swap_device_helper: Option<SwapDeviceHelper>,
137     ) -> Result<Self> {
138         let jail_warden: Box<dyn JailWarden> = match config.jail_config {
139             Some(_) => Box::new(
140                 JailWardenImpl::new(
141                     guest_memory,
142                     config,
143                     #[cfg(feature = "swap")]
144                     swap_device_helper,
145                 )
146                 .context("jail warden construction")?,
147             ),
148             None => Box::new(
149                 PermissiveJailWarden::new(
150                     guest_memory,
151                     config,
152                     #[cfg(feature = "swap")]
153                     swap_device_helper,
154                 )
155                 .context("jail warden construction")?,
156             ),
157         };
158         Ok(Self {
159             port_pool: PortPool::new(),
160             occupied_ports: HashMap::new(),
161             jail_warden,
162             rootbus_controller: None,
163         })
164     }
165 
166     /// Starts PciHotPlugManager. Required before any other commands.
167     ///
168     /// PciHotPlugManager::new must be called in a single-threaded context as it forks.
169     /// However, rootbus_controller is only available after VM boots when crosvm is multi-threaded.
170     ///
171     /// TODO(293801301): Remove unused after aarch64 support
172     #[allow(unused)]
set_rootbus_controller( &mut self, rootbus_controller: mpsc::Sender<PciRootCommand>, ) -> Result<()>173     pub fn set_rootbus_controller(
174         &mut self,
175         rootbus_controller: mpsc::Sender<PciRootCommand>,
176     ) -> Result<()> {
177         self.rootbus_controller = Some(rootbus_controller);
178         Ok(())
179     }
180 
181     /// Adds a hotplug capable port to manage.
182     ///
183     /// PciHotPlugManager assumes exclusive control for adding and removing devices to this port.
184     /// TODO(293801301): Remove unused_variables after aarch64 support
185     #[allow(unused)]
add_port(&mut self, port: Arc<Mutex<dyn HotPlugBus>>) -> Result<()>186     pub fn add_port(&mut self, port: Arc<Mutex<dyn HotPlugBus>>) -> Result<()> {
187         let port_lock = port.lock();
188         // Rejects hotplug bus with downstream devices.
189         if !port_lock.is_empty() {
190             bail!("invalid hotplug bus");
191         }
192         let pci_address = port_lock
193             .get_address()
194             .context("Hotplug bus PCI address missing")?;
195         // Reject hotplug buses not on rootbus, since otherwise the order of enumeration depends on
196         // the topology of PCI.
197         if pci_address.bus != 0 {
198             bail!("hotplug port on non-root bus not supported");
199         }
200         let downstream_bus = port_lock
201             .get_secondary_bus_number()
202             .context("cannot get downstream bus")?;
203         drop(port_lock);
204         self.port_pool.insert(PortStub {
205             pci_address,
206             downstream_bus,
207             port,
208             devices: HashMap::new(),
209         });
210         Ok(())
211     }
212 
213     /// hotplugs up to 8 PCI devices as "functions of a device" (in PCI Bus Device Function sense).
214     ///
215     /// returns the bus number of the bus on success.
hotplug_device<V: VmArch, Vcpu: VcpuArch>( &mut self, resource_carriers: Vec<ResourceCarrier>, linux: &mut RunnableLinuxVm<V, Vcpu>, resources: &mut SystemAllocator, ) -> Result<u8>216     pub fn hotplug_device<V: VmArch, Vcpu: VcpuArch>(
217         &mut self,
218         resource_carriers: Vec<ResourceCarrier>,
219         linux: &mut RunnableLinuxVm<V, Vcpu>,
220         resources: &mut SystemAllocator,
221     ) -> Result<u8> {
222         if resource_carriers.len() > 8 || resource_carriers.is_empty() {
223             bail!("PCI function count has to be 1 to 8 inclusive");
224         }
225         let (mut port_stub, port_availability) = self.port_pool.pop_first()?;
226         let downstream_bus = port_stub.downstream_bus;
227         if self.occupied_ports.contains_key(&downstream_bus) {
228             bail!("Downstream bus {} already used", downstream_bus);
229         }
230         for (func_num, mut resource_carrier) in resource_carriers.into_iter().enumerate() {
231             let device_address = PciAddress::new(0, downstream_bus as u32, 0, func_num as u32)?;
232             let hotplug_key = HotPlugKey::GuestDevice {
233                 guest_addr: device_address,
234             };
235             resource_carrier.allocate_address(device_address, resources)?;
236             let irq_evt = IrqLevelEvent::new()?;
237             let (pin, irq_num) = match downstream_bus % 4 {
238                 0 => (PciInterruptPin::IntA, 0),
239                 1 => (PciInterruptPin::IntB, 1),
240                 2 => (PciInterruptPin::IntC, 2),
241                 _ => (PciInterruptPin::IntD, 3),
242             };
243             resource_carrier.assign_irq(irq_evt.try_clone()?, pin, irq_num);
244             let (proxy_device, pid) = self
245                 .jail_warden
246                 .make_proxy_device(resource_carrier)
247                 .context("make proxy device")?;
248             let device_id = proxy_device.lock().device_id();
249             let device_name = proxy_device.lock().debug_label();
250             linux.irq_chip.as_irq_chip_mut().register_level_irq_event(
251                 irq_num,
252                 &irq_evt,
253                 IrqEventSource {
254                     device_id,
255                     queue_id: 0,
256                     device_name: device_name.clone(),
257                 },
258             )?;
259             let pid: u32 = pid.try_into().context("fork fail")?;
260             if pid > 0 {
261                 linux.pid_debug_label_map.insert(pid, device_name);
262             }
263             self.rootbus_controller
264                 .as_ref()
265                 .context("rootbus_controller not set: set_rootbus_controller not called?")?
266                 .send(PciRootCommand::Add(device_address, proxy_device))?;
267             port_stub
268                 .port
269                 .lock()
270                 .add_hotplug_device(hotplug_key, device_address);
271             port_stub
272                 .devices
273                 .insert(device_address, RecoverableResource { irq_num, irq_evt });
274         }
275         // Send hotplug signal after all functions are added.
276         match port_availability {
277             HotPlugAvailability::Now => port_stub.send_hotplug_signal()?,
278             HotPlugAvailability::After(cc_recvr) => {
279                 debug!(
280                     "Hotplug signal delayed since port {} is busy",
281                     &port_stub.pci_address,
282                 );
283                 port_stub.send_hotplug_signal_after_notification(cc_recvr)?;
284             }
285         }
286         self.occupied_ports.insert(downstream_bus, port_stub);
287         Ok(downstream_bus)
288     }
289 
290     /// Removes all hotplugged devices on the hotplug bus.
remove_hotplug_device<V: VmArch, Vcpu: VcpuArch>( &mut self, bus: u8, linux: &mut RunnableLinuxVm<V, Vcpu>, resources: &mut SystemAllocator, ) -> Result<()>291     pub fn remove_hotplug_device<V: VmArch, Vcpu: VcpuArch>(
292         &mut self,
293         bus: u8,
294         linux: &mut RunnableLinuxVm<V, Vcpu>,
295         resources: &mut SystemAllocator,
296     ) -> Result<()> {
297         // Unlike hotplug, HotPlugBus removes all downstream devices when eject signal is sent.
298         let cc_recvr = self.remove_device_from_port(bus)?;
299         let port_stub = self
300             .occupied_ports
301             .get_mut(&bus)
302             .context("invalid hotplug bus number")?;
303         // Remove all devices on the hotplug bus.
304         for (pci_address, recoverable_resource) in port_stub.devices.drain() {
305             self.rootbus_controller
306                 .as_ref()
307                 .context("rootbus_controller not set: set_rootbus_controller not called?")?
308                 .send(PciRootCommand::Remove(pci_address))?;
309             // port_stub.port does not have remove_hotplug_device method, as devices are removed
310             // when hot_unplug is called.
311             resources.release_pci(pci_address.bus, pci_address.dev, pci_address.func);
312             linux.irq_chip.unregister_level_irq_event(
313                 recoverable_resource.irq_num,
314                 &recoverable_resource.irq_evt,
315             )?;
316         }
317         if let Some(port_stub) = self.occupied_ports.remove(&bus) {
318             self.port_pool
319                 .insert_after_notification(port_stub, cc_recvr)?;
320         } else {
321             bail!("Failed to release bus {}", bus);
322         }
323         Ok(())
324     }
325 
326     /// Sends eject signal on the port, and removes downstream devices on the port.
remove_device_from_port(&self, bus: u8) -> Result<mpsc::Receiver<()>>327     fn remove_device_from_port(&self, bus: u8) -> Result<mpsc::Receiver<()>> {
328         let port_stub = self
329             .occupied_ports
330             .get(&bus)
331             .ok_or(anyhow!("invalid bus number for device removal: {}", bus))?;
332         let base_pci_address = PciAddress::new(0, bus as u32, 0, 0)?;
333         port_stub
334             .port
335             .lock()
336             .hot_unplug(base_pci_address)?
337             .context("no notifier for hot unplug completion.")
338     }
339 }
340 
341 /// PortPool is a pool available PCI ports where ports can be added asynchronously.
342 struct PortPool {
343     /// map of empty ports that are available
344     ports_available: BTreeMap<PciAddress, PortStub>,
345     /// map of ports that will soon be available
346     ports_pending: BTreeMap<PciAddress, (mpsc::Receiver<()>, PortStub)>,
347 }
348 
349 impl PortPool {
350     /// constructor
new() -> Self351     fn new() -> Self {
352         Self {
353             ports_available: BTreeMap::new(),
354             ports_pending: BTreeMap::new(),
355         }
356     }
357 
358     /// Insert a port_stub that is available immediately.
insert(&mut self, port_stub: PortStub) -> Result<()>359     fn insert(&mut self, port_stub: PortStub) -> Result<()> {
360         self.update_port_availability();
361         let pci_addr = port_stub.pci_address;
362         self.ports_available.insert(pci_addr, port_stub);
363         Ok(())
364     }
365 
366     /// Insert a port_stub that will be available after notification received. Returns immediately.
insert_after_notification( &mut self, port_stub: PortStub, cc_recvr: mpsc::Receiver<()>, ) -> Result<()>367     fn insert_after_notification(
368         &mut self,
369         port_stub: PortStub,
370         cc_recvr: mpsc::Receiver<()>,
371     ) -> Result<()> {
372         self.update_port_availability();
373         self.ports_pending
374             .insert(port_stub.pci_address, (cc_recvr, port_stub));
375         Ok(())
376     }
377 
378     /// Pop the first available port in the order of PCI enumeration. If no port is available now,
379     /// pop the first in the order of PCI enumeration.
pop_first(&mut self) -> Result<(PortStub, HotPlugAvailability)>380     fn pop_first(&mut self) -> Result<(PortStub, HotPlugAvailability)> {
381         self.update_port_availability();
382         if let Some((_, port_stub)) = self.ports_available.pop_first() {
383             return Ok((port_stub, HotPlugAvailability::Now));
384         }
385         match self.ports_pending.pop_first() {
386             Some((_, (cc_recvr, port_stub))) => {
387                 Ok((port_stub, HotPlugAvailability::After(cc_recvr)))
388             }
389             None => Err(anyhow!("No PCI ports available.")),
390         }
391     }
392 
393     /// Move pending ports to available ports if notification is received.
update_port_availability(&mut self)394     fn update_port_availability(&mut self) {
395         let mut new_ports_pending = BTreeMap::new();
396         while let Some((pci_addr, (cc_recvr, port_stub))) = self.ports_pending.pop_first() {
397             if cc_recvr.try_recv().is_ok() {
398                 let pci_addr = port_stub.pci_address;
399                 self.ports_available.insert(pci_addr, port_stub);
400             } else {
401                 new_ports_pending.insert(pci_addr, (cc_recvr, port_stub));
402             }
403         }
404         self.ports_pending = new_ports_pending;
405     }
406 }
407 
408 #[cfg(test)]
409 mod tests {
410     use devices::PcieRootPort;
411 
412     use super::*;
413 
new_mock_port_stub(pci_address: PciAddress) -> PortStub414     fn new_mock_port_stub(pci_address: PciAddress) -> PortStub {
415         let hotplug_port = PcieRootPort::new(0, true);
416         PortStub {
417             pci_address,
418             downstream_bus: 0,
419             port: Arc::new(Mutex::new(hotplug_port)),
420             devices: HashMap::new(),
421         }
422     }
423 
424     #[test]
port_pool_pop_before_completion()425     fn port_pool_pop_before_completion() {
426         let mut port_pool = PortPool::new();
427         let mock_port = new_mock_port_stub(PciAddress::new(0, 1, 0, 0).unwrap());
428         let (_cc_sender, cc_recvr) = mpsc::channel();
429         port_pool
430             .insert_after_notification(mock_port, cc_recvr)
431             .unwrap();
432         let (_port_stub, port_availability) = port_pool.pop_first().unwrap();
433         assert!(matches!(port_availability, HotPlugAvailability::After(_)));
434     }
435 
436     #[test]
port_pool_pop_after_completion()437     fn port_pool_pop_after_completion() {
438         let mut port_pool = PortPool::new();
439         let mock_port = new_mock_port_stub(PciAddress::new(0, 1, 0, 0).unwrap());
440         let (cc_sender, cc_recvr) = mpsc::channel();
441         port_pool
442             .insert_after_notification(mock_port, cc_recvr)
443             .unwrap();
444         cc_sender.send(()).unwrap();
445         let (_port_stub, port_availability) = port_pool.pop_first().unwrap();
446         assert!(matches!(port_availability, HotPlugAvailability::Now));
447     }
448 
449     #[test]
port_pool_pop_in_order()450     fn port_pool_pop_in_order() {
451         let mut port_pool = PortPool::new();
452         for bus_num in [2, 3, 4, 1, 0] {
453             let mock_port = new_mock_port_stub(PciAddress::new(0, bus_num, 0, 0).unwrap());
454             port_pool.insert(mock_port).unwrap();
455         }
456         for bus_num in 0..=4 {
457             assert_eq!(port_pool.pop_first().unwrap().0.pci_address.bus, bus_num);
458         }
459     }
460 }
461