0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于PCIe-Native机制的热插拔

openEuler 来源:openEuler 作者:openEuler 2022-09-06 10:32 次阅读

热插拔即带电插拔,在虚拟化场景下,热插拔就是在虚拟机运行过程中对磁盘网卡等设备进行动态调整。

常见的热插拔机制有 ACPI 机制的热插拔,PCIe-Native 机制的热插拔。ACPI 机制的热插拔依赖 ACPI 表,在 ACPI 表中会存放设备热插拔相关的信息。PCIe-Native 机制的热插拔是 PCI 规范中定义的,设备一般是热插到 Root Port 设备上,Root Port 设备可以认为是一个虚拟的桥设备,对应一个插槽。Root Port 设备本身不支持热插拔,因此需要在启动虚拟机前提前配置。

目前,StratoVirt 标准机型中实现了基于 PCIe-Native 机制的热插拔。支持热插拔的设备包括磁盘、网卡、PCI 直通设备。

热插拔的整体流程如下:

3699a45a-2d1a-11ed-ba43-dac502259ad0.png

对于热插主要分为两步:

  1. 用户通过 QMP 下发 device_add 命令,StratoVirt 收到命令后会进行设备的实例化,然后插入到对应的 Root Port 设备上。
  2. Root Port 设备更新相关的寄存器配置,然后发送中断通知虚拟机内驱动处理。

对于热拔也可以分为两步:

  1. 用户通过 QMP 下发 device_del 命令,StratoVirt 收到命令后,更新 Root Port 中的寄存器,然后发送中断通知虚拟机内驱动处理。
  2. 虚拟机内驱动处理后会回写寄存器,触发 StratoVirt 侧销毁相应设备。

具体实现

在 StratoVirt 的 pci/src/hotplug.rs 文件中定义了热插拔特性,其中 plug 函数对应热插操作,用于热插设备。unplug_request 函数对应热拔操作,用于发起热拔设备请求,这里只是通知虚拟机内驱动去处理热拔请求,还未移除设备,可以理解为是一个异步请求。当虚拟机内驱动处理完成后,写寄存器触发设备下线后,会回调 unplug 函数用于销毁设备。

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<()>;}

热插实现

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>) -> 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 函数中会调用 MSIX 中断的 notify 函数发送中断到虚拟机内,虚拟机内 pciehp 驱动收到中断后会处理相关的热插请求。

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");    }}

热拔实现

对于设备热拔请求的逻辑主要在 unplug_request 函数,该函数负责更新寄存器,并且通过调用 hotplug_event_notify 函数发送中断通知虚拟机内驱动处理设备热拔请求。

unplug_request 函数里主要是清零了 Link 状态寄存器中的 PCI_EXP_LNKSTA_DLLLA 标记位,并且在 Slot 状态寄存器中的设置了 PCI_EXP_HP_EV_ABP 标记位。从这里也可以发现,其实无论是热插请求还是热拔请求,都是通过 Attention Button Pressed(对应 PCI_EXP_HP_EV_ABP)事件触发的,虚拟机内驱动会根据设备的在位状态来判断是热插请求还是热拔请求。

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(())    }}

对于热拔设备,StratoVirt 侧在更新寄存器发送中断通知虚拟机内驱动后,实际上还没有真正的移除设备,而是等到虚拟机内驱动处理后回写寄存器通知 StratoVirt 侧下线设备后,才会真正销毁设备。

虚拟机内驱动写 Root Port 寄存器会调用到 write_config 函数,在 write_config 函数里会调用 do_unplug 函数来处理热拔设备相关的逻辑。

    fn write_config(&mut self, offset: usize, data: &[u8]) {        ...
        self.do_unplug(offset, end, old_ctl);    }

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();}

在调用 remove_devices 函数移除设备之后,调用 update_register_status 函数更新寄存器的状态,主要是清理了 Link 状态和设备在位状态,并且设置了 Presence Detect Changed(对应 PCI_EXP_HP_EV_PDC)标记位表示设备在位状态发生了变化。

/// 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 还会设置 Command Completed(对应 PCI_EXP_HP_EV_CCI)表示命令处理完成,最后再发送中断通知虚拟机内驱动。至此,整个设备热拔流程就结束了。

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");    }}
符号 描述
PCI_EXP_HP_EV_CCI Command Completed 表示命令处理完成,可以处理下一条命令

总结

PCIe Native 机制的热插拔主要是通过 Root Port 设备上的寄存器来表示不同状态,通过中断来通知虚拟机,从而实现了设备的热插拔。

审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 热插拔
    +关注

    关注

    2

    文章

    224

    浏览量

    37354
  • PCIe
    +关注

    关注

    15

    文章

    1239

    浏览量

    82662
  • 虚拟机
    +关注

    关注

    1

    文章

    917

    浏览量

    28204

原文标题:StratoVirt 中的 PCI 设备热插拔实现

文章出处:【微信号:openEulercommunity,微信公众号:openEuler】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    高精度热插拔和电源监控

    本内容提供了高精度热插拔和电源监控,介绍什么是热插拔和利用热插拔进行的设计方案及电源监控知识。
    发表于 11-04 10:00 1676次阅读
    高精度<b class='flag-5'>热插拔</b>和电源监控

    热插拔是什么?热插拔有哪些特点?

    什么是热插拔热插拔(hot-plugging或Hot Swap)即带电插拔热插拔功能就是允许用户在不关闭系统,不切断电源的情况下取出和更换损坏的硬盘、电源或板卡等部件,从而提高了系
    发表于 12-13 10:53

    即插即用和热插拔的区别

    本帖最后由 eehome 于 2013-1-5 10:01 编辑 “即插即用”是指安装了设备之后系统可以自动配置和管理设备,不需要人工处理即可使用。“热插拔”是指可以在开机状态下将设备与主机
    发表于 10-23 10:26

    如何对BMS单元连接进行热插拔

    过渡到热插拔测试,一些故障实际上是针对热插拔失效机制的。观察表明,2、3、4故障与热插拔故障检测有关。当然,由于不同的热插拔连接模式的出现,
    发表于 09-07 18:20

    热插拔PCI总线

    DN155- 热插拔PCI总线
    发表于 05-28 12:21

    热插拔CompactPCI总线

    DN200- 热插拔CompactPCI总线
    发表于 07-17 06:32

    热插拔的基本原理是什么?热插拔有哪些功能?

    热插拔的基本原理是什么?热插拔有哪些功能?
    发表于 05-24 06:01

    使热插拔与电子熔丝的优势

    使用热插拔控制器的优势电子熔丝与热插拔控制器之间的主要区别是热插拔是一种能够驱动外部FET的控制器(如图1所示)。FET通过热插拔控制器中的控制逻辑进行开启和关闭,以调节负载处的电源供
    发表于 11-17 07:12

    热插拔装置软件

    热插拔装置软件USB Safely Remove是一款支持热插拔装置和迅速切断一个公用的热插拔装置的软件。
    发表于 04-23 09:32 151次下载

    热插拔

    热插拔              热插拔(hot-plugging或Hot Swap)功能就是允许用户在不关闭系统,不切断电源的情况下取出和更换
    发表于 12-17 11:41 632次阅读

    PCIe总线的热插拔机制

    当然,热插拔不仅仅是硬件的事,其需要软硬件协同实现。要想实现热插拔功能,操作系统、主板热插拔驱动器、PCIe卡设备驱动以及PCIe卡硬件功能
    的头像 发表于 09-06 09:20 2w次阅读

    PCIe引脚PRSNT与热插拔

    热插拔的基本目的是要让PCIe设备按照规定的顺序、原则,从系统中移除或插入到系统中来,并能正常的工作,且不影响系统的正常运行。事实上,PCIe热插拔”的关键目的就是为前面面所提到的系
    的头像 发表于 12-14 10:59 4977次阅读

    热插拔和非热插拔的区别

    热插拔和非热插拔的区别  热插拔和非热插拔是指电子设备或组件在工作状态下是否可以进行插拔操作的一种分类。
    的头像 发表于 12-28 10:01 2977次阅读

    键盘热插拔和非热插拔的区别

    键盘热插拔和非热插拔的区别 键盘是计算机外设设备之一,热插拔是指在计算机运行中插入或拔出设备而无需重启计算机,非热插拔则需要重启计算机才能生效。键盘
    的头像 发表于 02-02 17:34 1w次阅读

    PCIe热插拔机制介绍

    前言本文主要讲述PCIe热插拔机制,通过图形方式方便读者快速掌握。 一、概述 如果在PCIe设备不支持热插拔的条件下,在不断电的情况下
    的头像 发表于 11-20 09:07 388次阅读
    <b class='flag-5'>PCIe</b><b class='flag-5'>热插拔</b><b class='flag-5'>机制</b>介绍