pub trait HotplugOps: Send { /// Plug device, usually called when hot plug device in device_add. fn plug(&mut self, dev: &Arc<Mutex<dyn PciDevOps>>) -> Result<()>;
/// Unplug device request, usually called when hot unplug device in device_del. /// Only send unplug request to the guest OS, without actually removing the device. fn unplug_request(&mut self, dev: &Arc<Mutex<dyn PciDevOps>>) -> Result<()>;
/// Remove the device. fn unplug(&mut self, dev: &Arc<Mutex<dyn PciDevOps>>) -> Result<()>;}
热插实现
StratoVirt 里通过给 RootPort 实现了 HotplugOps 特性,使得 PCI 设备能够热插到 Root Port 设备上。
设备热插的主要实现逻辑在 plug 函数里。首先获取了设备的 devfn 号,也就是 Device 号和 Function 号,目前热插只支持 Device 号和 Function 号都为 0 的设备。因此这里做了判断。
然后会在 RootPort 设备的 PCI 配置空间中的 PCI Express Capability(PCI 配置空间和 PCI Express Capability 寄存器定义可以参考 PCI 规范)中设置 Slot 状态寄存器和 Link 状态寄存器,然后通过 hotplug_event_notify 函数发送中断通知虚拟机。这里热插设备主要是通过 Attention Button Pressed(对应 PCI_EXP_HP_EV_ABP)事件触发的。
这里简单介绍下不同标记位的含义。
符号 描述 PCI_EXP_SLTSTA Slot Status Register 表示 Slot 状态寄存器,不同的位表示 Slot 不同的状态 PCI_EXP_SLTSTA_PDS Presence Detect State 表示 Slot 上设备的在位状态,置 1 表示在位 PCI_EXP_HP_EV_PDC Presence Detect Changed 表示 Slot 上设备在位状态是否发生变化 PCI_EXP_HP_EV_ABP Attention Button Pressed 表示 Attention 按钮被按下,该按钮用于触发热插拔操作 PCI_EXP_LNKSTA Link Status Register 表示 Link 状态的寄存器 PCI_EXP_LNKSTA_DLLLA Data Link Layer Link Active 表示数据链路控制和管理状态,置 1 表示处于 Active 状态
impl HotplugOps for RootPort { fn plug(&mut self, dev: &Arc<Mutex<dyn PciDevOps>>) -> Result<()> { let devfn = dev .lock() .unwrap() .devfn() .chain_err(|| "Failed to get devfn")?; // Only if devfn is equal to 0, hot plugging is supported. if devfn == 0 { let offset = self.config.ext_cap_offset; le_write_set_value_u16( &mut self.config.config, (offset + PCI_EXP_SLTSTA) as usize, PCI_EXP_SLTSTA_PDS | PCI_EXP_HP_EV_PDC | PCI_EXP_HP_EV_ABP, )?; le_write_set_value_u16( &mut self.config.config, (offset + PCI_EXP_LNKSTA) as usize, PCI_EXP_LNKSTA_NLW | PCI_EXP_LNKSTA_DLLLA, )?; self.hotplug_event_notify(); } Ok(()) }}
do_unplug 函数里首先保证了写入的寄存器是 Slot Control 寄存器,否则直接返回,不做处理。然后判断在设备当前在位的情况下,写入的寄存器标记位为 PCI_EXP_SLTCTL_PWR_IND_OFF 和 PCI_EXP_SLTCTL_PCC 时,并且这两个标记位发生了变化,也就是写入之前的没有这两个标记位,上述条件都满足时,会调用 remove_devices 函数开始真正销毁设备。
符号 描述 PCI_EXP_SLTCTL_PCC Power Controller Control 表示电源管理状态,置 1 表示上电状态 PCI_EXP_SLTCTL_PWR_IND_OFF Power Indicator off 表示是否允许移除设备,置 1 表示设备允许被移除
fn do_unplug(&mut self, offset: usize, end: usize, old_ctl: u16) { let cap_offset = self.config.ext_cap_offset; // Only care the write config about slot control if !ranges_overlap( offset, end, (cap_offset + PCI_EXP_SLTCTL) as usize, (cap_offset + PCI_EXP_SLTCTL + 2) as usize, ) { return; }
let status = le_read_u16(&self.config.config, (cap_offset + PCI_EXP_SLTSTA) as usize).unwrap(); let val = le_read_u16(&self.config.config, offset).unwrap(); // Only unplug device when the slot is on // Don't unplug when slot is off for guest OS overwrite the off status before slot on. if (status & PCI_EXP_SLTSTA_PDS != 0) && (val as u16 & PCI_EXP_SLTCTL_PCC == PCI_EXP_SLTCTL_PCC) && (val as u16 & PCI_EXP_SLTCTL_PWR_IND_OFF == PCI_EXP_SLTCTL_PWR_IND_OFF) && (old_ctl & PCI_EXP_SLTCTL_PCC != PCI_EXP_SLTCTL_PCC || old_ctl & PCI_EXP_SLTCTL_PWR_IND_OFF != PCI_EXP_SLTCTL_PWR_IND_OFF) { self.remove_devices();
if let Err(e) = self.update_register_status() { error!("{}", e.display_chain()); error!("Failed to update register status"); } }
self.hotplug_command_completed(); self.hotplug_event_notify();}