熱插拔即帶電插拔,在虛擬化場(chǎng)景下,熱插拔就是在虛擬機(jī)運(yùn)行過(guò)程中對(duì)磁盤(pán)網(wǎng)卡等設(shè)備進(jìn)行動(dòng)態(tài)調(diào)整。
常見(jiàn)的熱插拔機(jī)制有 ACPI 機(jī)制的熱插拔,PCIe-Native 機(jī)制的熱插拔。ACPI 機(jī)制的熱插拔依賴 ACPI 表,在 ACPI 表中會(huì)存放設(shè)備熱插拔相關(guān)的信息。PCIe-Native 機(jī)制的熱插拔是 PCI 規(guī)范中定義的,設(shè)備一般是熱插到 Root Port 設(shè)備上,Root Port 設(shè)備可以認(rèn)為是一個(gè)虛擬的橋設(shè)備,對(duì)應(yīng)一個(gè)插槽。Root Port 設(shè)備本身不支持熱插拔,因此需要在啟動(dòng)虛擬機(jī)前提前配置。
目前,StratoVirt 標(biāo)準(zhǔn)機(jī)型中實(shí)現(xiàn)了基于 PCIe-Native 機(jī)制的熱插拔。支持熱插拔的設(shè)備包括磁盤(pán)、網(wǎng)卡、PCI 直通設(shè)備。
熱插拔的整體流程如下:
對(duì)于熱插主要分為兩步:
- 用戶通過(guò) QMP 下發(fā) device_add 命令,StratoVirt 收到命令后會(huì)進(jìn)行設(shè)備的實(shí)例化,然后插入到對(duì)應(yīng)的 Root Port 設(shè)備上。
- Root Port 設(shè)備更新相關(guān)的寄存器配置,然后發(fā)送中斷通知虛擬機(jī)內(nèi)驅(qū)動(dòng)處理。
對(duì)于熱拔也可以分為兩步:
- 用戶通過(guò) QMP 下發(fā) device_del 命令,StratoVirt 收到命令后,更新 Root Port 中的寄存器,然后發(fā)送中斷通知虛擬機(jī)內(nèi)驅(qū)動(dòng)處理。
- 虛擬機(jī)內(nèi)驅(qū)動(dòng)處理后會(huì)回寫(xiě)寄存器,觸發(fā) StratoVirt 側(cè)銷毀相應(yīng)設(shè)備。
具體實(shí)現(xiàn)
在 StratoVirt 的 pci/src/hotplug.rs 文件中定義了熱插拔特性,其中 plug 函數(shù)對(duì)應(yīng)熱插操作,用于熱插設(shè)備。unplug_request 函數(shù)對(duì)應(yīng)熱拔操作,用于發(fā)起熱拔設(shè)備請(qǐng)求,這里只是通知虛擬機(jī)內(nèi)驅(qū)動(dòng)去處理熱拔請(qǐng)求,還未移除設(shè)備,可以理解為是一個(gè)異步請(qǐng)求。當(dāng)虛擬機(jī)內(nèi)驅(qū)動(dòng)處理完成后,寫(xiě)寄存器觸發(fā)設(shè)備下線后,會(huì)回調(diào) unplug 函數(shù)用于銷毀設(shè)備。
pub trait HotplugOps: Send {
/// Plug device, usually called when hot plug device in device_add.
fn plug(&mut self, dev: &Arc
>) -> 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
>) -> Result<()>; /// Remove the device.
fn unplug(&mut self, dev: &Arc
>) -> Result<()>; }
熱插實(shí)現(xiàn)
StratoVirt 里通過(guò)給 RootPort 實(shí)現(xiàn)了 HotplugOps 特性,使得 PCI 設(shè)備能夠熱插到 Root Port 設(shè)備上。
設(shè)備熱插的主要實(shí)現(xiàn)邏輯在 plug 函數(shù)里。首先獲取了設(shè)備的 devfn 號(hào),也就是 Device 號(hào)和 Function 號(hào),目前熱插只支持 Device 號(hào)和 Function 號(hào)都為 0 的設(shè)備。因此這里做了判斷。
然后會(huì)在 RootPort 設(shè)備的 PCI 配置空間中的 PCI Express Capability(PCI 配置空間和 PCI Express Capability 寄存器定義可以參考 PCI 規(guī)范)中設(shè)置 Slot 狀態(tài)寄存器和 Link 狀態(tài)寄存器,然后通過(guò) hotplug_event_notify 函數(shù)發(fā)送中斷通知虛擬機(jī)。這里熱插設(shè)備主要是通過(guò) Attention Button Pressed(對(duì)應(yīng) PCI_EXP_HP_EV_ABP)事件觸發(fā)的。
這里簡(jiǎn)單介紹下不同標(biāo)記位的含義。
符號(hào) | 描述 |
---|---|
PCI_EXP_SLTSTA | Slot Status Register 表示 Slot 狀態(tài)寄存器,不同的位表示 Slot 不同的狀態(tài) |
PCI_EXP_SLTSTA_PDS | Presence Detect State 表示 Slot 上設(shè)備的在位狀態(tài),置 1 表示在位 |
PCI_EXP_HP_EV_PDC | Presence Detect Changed 表示 Slot 上設(shè)備在位狀態(tài)是否發(fā)生變化 |
PCI_EXP_HP_EV_ABP | Attention Button Pressed 表示 Attention 按鈕被按下,該按鈕用于觸發(fā)熱插拔操作 |
PCI_EXP_LNKSTA | Link Status Register 表示 Link 狀態(tài)的寄存器 |
PCI_EXP_LNKSTA_DLLLA | Data Link Layer Link Active 表示數(shù)據(jù)鏈路控制和管理狀態(tài),置 1 表示處于 Active 狀態(tài) |
impl HotplugOps for RootPort {
fn plug(&mut self, dev: &Arc
>) -> 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(())
}
}
在 hotplug_event_notify 函數(shù)中會(huì)調(diào)用 MSIX 中斷的 notify 函數(shù)發(fā)送中斷到虛擬機(jī)內(nèi),虛擬機(jī)內(nèi) pciehp 驅(qū)動(dòng)收到中斷后會(huì)處理相關(guān)的熱插請(qǐng)求。
fn hotplug_event_notify(&mut self) {
if let Some(msix) = self.config.msix.as_mut() {
msix.lock()
.unwrap()
.notify(0, self.dev_id.load(Ordering::Acquire));
} else {
error!("Failed to send interrupt: msix does not exist");
}
}
熱拔實(shí)現(xiàn)
對(duì)于設(shè)備熱拔請(qǐng)求的邏輯主要在 unplug_request 函數(shù),該函數(shù)負(fù)責(zé)更新寄存器,并且通過(guò)調(diào)用 hotplug_event_notify 函數(shù)發(fā)送中斷通知虛擬機(jī)內(nèi)驅(qū)動(dòng)處理設(shè)備熱拔請(qǐng)求。
unplug_request 函數(shù)里主要是清零了 Link 狀態(tài)寄存器中的 PCI_EXP_LNKSTA_DLLLA 標(biāo)記位,并且在 Slot 狀態(tài)寄存器中的設(shè)置了 PCI_EXP_HP_EV_ABP 標(biāo)記位。從這里也可以發(fā)現(xiàn),其實(shí)無(wú)論是熱插請(qǐng)求還是熱拔請(qǐng)求,都是通過(guò) Attention Button Pressed(對(duì)應(yīng) PCI_EXP_HP_EV_ABP)事件觸發(fā)的,虛擬機(jī)內(nèi)驅(qū)動(dòng)會(huì)根據(jù)設(shè)備的在位狀態(tài)來(lái)判斷是熱插請(qǐng)求還是熱拔請(qǐng)求。
impl HotplugOps for RootPort {
fn unplug_request(&mut self, dev: &Arc
>) -> Result<()> { let devfn = dev
.lock()
.unwrap()
.devfn()
.chain_err(|| "Failed to get devfn")?;
if devfn != 0 {
return self.unplug(dev);
}
let offset = self.config.ext_cap_offset;
le_write_clear_value_u16(
&mut self.config.config,
(offset + PCI_EXP_LNKSTA) as usize,
PCI_EXP_LNKSTA_DLLLA,
)?;
let mut slot_status = PCI_EXP_HP_EV_ABP;
if let Some(&true) = FAST_UNPLUG_FEATURE.get() {
slot_status |= PCI_EXP_HP_EV_PDC;
}
le_write_set_value_u16(
&mut self.config.config,
(offset + PCI_EXP_SLTSTA) as usize,
slot_status,
)?;
self.hotplug_event_notify();
Ok(())
}
}
對(duì)于熱拔設(shè)備,StratoVirt 側(cè)在更新寄存器發(fā)送中斷通知虛擬機(jī)內(nèi)驅(qū)動(dòng)后,實(shí)際上還沒(méi)有真正的移除設(shè)備,而是等到虛擬機(jī)內(nèi)驅(qū)動(dòng)處理后回寫(xiě)寄存器通知 StratoVirt 側(cè)下線設(shè)備后,才會(huì)真正銷毀設(shè)備。
虛擬機(jī)內(nèi)驅(qū)動(dòng)寫(xiě) Root Port 寄存器會(huì)調(diào)用到 write_config 函數(shù),在 write_config 函數(shù)里會(huì)調(diào)用 do_unplug 函數(shù)來(lái)處理熱拔設(shè)備相關(guān)的邏輯。
fn write_config(&mut self, offset: usize, data: &[u8]) {
...
self.do_unplug(offset, end, old_ctl);
}
do_unplug 函數(shù)里首先保證了寫(xiě)入的寄存器是 Slot Control 寄存器,否則直接返回,不做處理。然后判斷在設(shè)備當(dāng)前在位的情況下,寫(xiě)入的寄存器標(biāo)記位為 PCI_EXP_SLTCTL_PWR_IND_OFF 和 PCI_EXP_SLTCTL_PCC 時(shí),并且這兩個(gè)標(biāo)記位發(fā)生了變化,也就是寫(xiě)入之前的沒(méi)有這兩個(gè)標(biāo)記位,上述條件都滿足時(shí),會(huì)調(diào)用 remove_devices 函數(shù)開(kāi)始真正銷毀設(shè)備。
符號(hào) | 描述 |
---|---|
PCI_EXP_SLTCTL_PCC | Power Controller Control 表示電源管理狀態(tài),置 1 表示上電狀態(tài) |
PCI_EXP_SLTCTL_PWR_IND_OFF | Power Indicator off 表示是否允許移除設(shè)備,置 1 表示設(shè)備允許被移除 |
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();
}
在調(diào)用 remove_devices 函數(shù)移除設(shè)備之后,調(diào)用 update_register_status 函數(shù)更新寄存器的狀態(tài),主要是清理了 Link 狀態(tài)和設(shè)備在位狀態(tài),并且設(shè)置了 Presence Detect Changed(對(duì)應(yīng) PCI_EXP_HP_EV_PDC)標(biāo)記位表示設(shè)備在位狀態(tài)發(fā)生了變化。
/// Update register when the guest OS trigger the removal of the device.
fn update_register_status(&mut self) -> Result<()> {
let cap_offset = self.config.ext_cap_offset;
le_write_clear_value_u16(
&mut self.config.config,
(cap_offset + PCI_EXP_SLTSTA) as usize,
PCI_EXP_SLTSTA_PDS,
)?;
le_write_clear_value_u16(
&mut self.config.config,
(cap_offset + PCI_EXP_LNKSTA) as usize,
PCI_EXP_LNKSTA_DLLLA,
)?;
le_write_set_value_u16(
&mut self.config.config,
(cap_offset + PCI_EXP_SLTSTA) as usize,
PCI_EXP_SLTSTA_PDC,
)?;
Ok(())
}
在更新完寄存器后,在 hotplug_command_completed 還會(huì)設(shè)置 Command Completed(對(duì)應(yīng) PCI_EXP_HP_EV_CCI)表示命令處理完成,最后再發(fā)送中斷通知虛擬機(jī)內(nèi)驅(qū)動(dòng)。至此,整個(gè)設(shè)備熱拔流程就結(jié)束了。
fn hotplug_command_completed(&mut self) {
if let Err(e) = le_write_set_value_u16(
&mut self.config.config,
(self.config.ext_cap_offset + PCI_EXP_SLTSTA) as usize,
PCI_EXP_HP_EV_CCI,
) {
error!("{}", e.display_chain());
error!("Failed to write command completed");
}
}
符號(hào) | 描述 |
---|---|
PCI_EXP_HP_EV_CCI | Command Completed 表示命令處理完成,可以處理下一條命令 |
總結(jié)
PCIe Native 機(jī)制的熱插拔主要是通過(guò) Root Port 設(shè)備上的寄存器來(lái)表示不同狀態(tài),通過(guò)中斷來(lái)通知虛擬機(jī),從而實(shí)現(xiàn)了設(shè)備的熱插拔。
審核編輯:湯梓紅
-
熱插拔
+關(guān)注
關(guān)注
2文章
226瀏覽量
37654 -
PCIe
+關(guān)注
關(guān)注
15文章
1266瀏覽量
83273 -
虛擬機(jī)
+關(guān)注
關(guān)注
1文章
949瀏覽量
28474
原文標(biāo)題:StratoVirt 中的 PCI 設(shè)備熱插拔實(shí)現(xiàn)
文章出處:【微信號(hào):openEulercommunity,微信公眾號(hào):openEuler】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
高精度熱插拔和電源監(jiān)控
![高精度<b class='flag-5'>熱插拔</b>和電源監(jiān)控](https://file1.elecfans.com//web2/M00/A6/0F/wKgZomUMO4aAUpkNAAAbwTUi07U040.jpg)
熱插拔是什么?熱插拔有哪些特點(diǎn)?
即插即用和熱插拔的區(qū)別
如何對(duì)BMS單元連接進(jìn)行熱插拔
使熱插拔與電子熔絲的優(yōu)勢(shì)
熱插拔
PCIe總線的熱插拔機(jī)制
PCIe引腳PRSNT與熱插拔
熱插拔和非熱插拔的區(qū)別
熱插拔是什么原理
![<b class='flag-5'>熱插拔</b>是什么原理](https://file1.elecfans.com/web2/M00/BC/BF/wKgZomWl8NOAWafsAAAepPr5o7Q707.jpg)
評(píng)論