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