PCIE硬件支持
如图所示,上面是一个pcie设备,下面是主板对应的pcie插槽。
根据CEM标准,pcie设备金手指的两边有两个相对来说短一点的边带信号,PRSNT1#和PRSNT2#,这两个就是用来支持pcie设备热插拔的,可以看到这两个信号是直接相连的。
而在pcie插槽上,在pcie设备PRSNT1#和PRSNT2#对应的位置也有两个信号。在不插设备时,pcie插槽上的PRSNT1#是一直接地的,也就是一直在低电平,而PRSNT2#由于PULL-UP(这个我搜了一下什么意思,大概就是把这个信号通过一个电阻直接接到电源上以保持高电平状态?)一直处于高电平。
所以,当上面的pcie设备插入pcie插槽的时候,pcie插槽上的PRSNT1#和PRSNT2#短路连接,而PRSNT1#一直接地,所以pcie插槽上的PRSNT2#被短路从高电平变成低电平,主板上的hotplug control logic捕获到这个电平变化,通知downstream port发现了一个presence detect 事件,downstream port发送中断消息给RC。然后到cpu,去处理设备热插。热拔pcie设备也就好理解了,pcie设备拔出时,pcie插槽上的PRSNT2#不再被短路,恢复高电平,hotplug control logic肯定能捕获到这个低电平到高电平的转换,就知道又发生了presence detect 事件。
为什么pcie设备上的PRSNT1#和PRSNT2#信号要做的比金手指短一些呢?大佬们太有智慧了,我理解是这样的:设备热插的时候,PRSNT1#和PRSNT2#信号短一些能保证其他金手指彻底与pcie插槽连接上之后才短路PRSNT2#通知热插事件,防止出现向系统发出热插中断信号时设备还没彻底与pcie插槽连接的情况;设备热拔的时候,PRSNT1#和PRSNT2#信号能够先与pcie插槽断开连接,这时候PRSNT2#信号恢复高电平向系统发热拔中断,在这个时间点到其他金手指拔出的这个时间差,能够让系统去处理热拔中断,清理残留等内容。
热插拔方法
我认为以下两种方法主要还是在热拔设备的时候有所区别
通知式热插拔
这个是一种比较温和的不会对系统或者设备造成伤害的方式。也就是在热插拔的时候需要先通知内核。
这个我不知道理解的对不对,关于attention button和一个sys接口/sys/bus/pci/slots/
##按下attetion button
##echo 0 > /sys/bus/pci/slots/<SLOT_NUM>/power
暴力热插拔
这个就是字面上的意思,直接暴力拔出设备,也就是surprise remove。所以我觉得应该也就是上面说的,只能在PRSNT1#和PRSNT2#与pcie插槽断开而其他金手指还没与pcie插槽断开的这段时间处理设备拔出操作,而且因为没有提前通知,所以可能会有IO残留之类的东西,容易出现设备或者系统损坏。
组成部件
为了支持pcie热插拔,pcie协议定义了一些列需要实现的组件,这个直接抄过大佬的过来了,倒不用记,看代码的时候做个参考就好
组件 | 目的 |
---|---|
Indicator | show the power and attention state of the slot. 标准模型中定义两个 indicators 和一个 Attention Indicator。都会有三种状态:ON;OFF;Blinking。 |
Attention Indicator是黄色的灯,用来指示出现了操作的问题,或者表示 hotplug slot 已经被识别到了,手动操作可以很容易定位到。 • ON:表示热插拔槽位故障 • OFF:表示一切正常 • BLINKING:表示热插拔流程正在执行 | |
Power Indicator是绿色的灯,表示在slot一个接口显示线对用户的操作产生了响应。 • OFF:表示槽位下电,可以进行设备的热插或热拔 • ON:表示热插拔操作已完成,槽位上电,不可以进行热插或热拔 • BLINKING:表示此时正在上电或下电槽位,或者此时 attention button 下,正在等待反馈,或者表示hot-plus操作的初始化 客户需求:表示槽位下电,可以进行设备的热插或热拔 | |
Manually-operated Retention Latch (MRL) | Holds adds-in cards in place. Allows the Port and system software to detect the MRL being opened. MRL是一种手动操作保留机制。保持插入卡在slot上,防止用户移除卡。系统添加了一个MRI sensor,以便侦测每个port对应的 slotR的MRL。 |
Electromechanical Interlock | Prevents removal of add-in cards while slot is powered. 一种互斥机制,确保在热插拔流程都执行完成后 PCle PCle设备才可被物理移除。 |
Attention Button | Allows user to request Hot-Plug operations. 是 hotplug 中定义的一个开关按钮,一般会在 slot 上或者在卡片上,按一下表明要做一个 hotplug 动作或者 removal 动作。 |
Slot Numbering | Provides visual identification of slots. 槽位编号,由底板号(Classic Number)和物理槽位号(Physical Slot Number)组成,可在用户接口上显示。 |
热插拔过程
以使用pciehp驱动为例,必须打开CONFIG_HOTPLUG_PCI_PCIE才行
相关驱动初始化
pcie_hp_init
->pcie_port_service_register ##注册一个hpdriver_portdrv,这是一个static的pcie_port_service_driver结构体,里面有一些probe remove函数,就是pciehp_probe、pciehp_remove
->设置hpdriver_portdrv->driver,也是一些probe remove之类的,然后看了下实现,实际上应该是去其实还是去调hpdriver_portdrv的probe、remove
->driver_register
->bus_add_driver
->driver_attach
...->pciehp_probe ##一堆调用之后,会调到probe
->pcie_init ##初始化一个controller结构体
->init_slot #初始化controller的hotplug_slot.ops,包括一些slot的操作,比如enable disable get_poer_status等
->__pci_hp_initialize
->pci_create_slot #创建用于hotplug的slot
->pcie_init_notification
->pciehp_request_irq #注册irq
->request_threaded_irq #如果是pciehp_poll_mode就注册一个pciehp_poll内核线程,否则调用request_threaded_irq去注册线程化的irq
->__setup_irq
->setup_irq_thread #创建内核线程,用于处理硬中断之后的任务
->pcie_enable_notification
->pci_hp_add #发uevent通知userspace hotplug slot
大概能知道,这块就是初始化相关结构体,创建slot通知userspace,申请线程化的irq,其中pciehp_isr用于处理硬中断,pciehp_ist是内核线程,处理硬中断之后的任务。
(对pcie设备模型还不太了解,这些结构体有什么用,以及怎么组织管理,怎么串联起来,以及代表什么还不太清楚,后续还得看一下pcie设备模型这块)
真正热插拔处理
如上所说,当热插拔一个设备的时候,由两边的信号电平变化产生中断,其实就是去执行的pciehp_isr
pciehp_isr
->pm_runtime_get_noresume(parent); #如果存在父设备,需要先保证父设备时active的
->pcie_capability_read_word #获取设备状态,这些状态应该和组成部件提到的那些有关
->atomic_or(events, &ctrl->pending_events); #把状态保存到ctl中去,以便让irq线程处理
->return IRQ_WAKE_THREAD #唤醒pciehp_ist
所以硬中断中做的就是:1.确保设备状态没问题,active的可以访问2.保存状态,到pciehp_ist线程中处理
pciehp_ist
->pciehp_handle_button_press #如果PCI_EXP_SLTSTA_ABP,由attention button被按下,去处理这个事件
->switch (ctrl->state) #根据不同的状态去处理
->schedule_delayed_work(&ctrl->button_work, 5 * HZ) #如果是ON,说明要进行热拔,设置state为BLINKINGOFF_STATE,触发button work;state为OFF时相反。同时设置对应的Indicator
->pciehp_queue_pushbutton_work
->pciehp_request #如果是BLINKINGOFF_STATE,则请求DISABLE_SLOT关掉slot;如果是BLINKINGON_STATE,则请求PCI_EXP_SLTSTA_PDC,也就是presence detect
->irq_wake_thread
->__irq_wake_thread
->wake_up_process #其实还是去执行pciehp_ist,所以可以看出来,这就是一个状态机的维护
->cancel_delayed_work #如果是BLINKINGOFF_STATE,说明正在掉电,取消这次请求,恢复ON_STATE;BLINKINGON_STATE相反。同时设置对应的Indicator
->pciehp_set_indicators #监测到PCI_EXP_SLTSTA_PFD power fault,就设置一下indicators就不继续处理了
->pciehp_ignore_dpc_link_change #忽略因为DPC引起的link up/down事件,如果有PCI_EXP_SLTSTA_DLLSC链路状态变化且是从DPC恢复的且此时设备状态是ON_STATE,说明是DPC引起的链路状态变化
->pciehp_handle_disable_request #有DISABLE_SLOT事件,也就是attention button/软件通知热拔设备要做的disbale slot
->如果是在BLINKING状态,cancel_delayed_work取消delayed work,设置状态为POWEROFF
->pciehp_disable_slot
->__pciehp_disable_slot
->remove_board
->pciehp_unconfigure_device #remove PCI devices below a hotplug bridge,第二个参数presence,true是safe remove,false是surprise remove
->如果是surprise remove,调用pci_walk_bus把设备标记为disconnected
->pci_lock_rescan_remove #锁总线
->list_for_each_entry_safe_reverse #反向遍历设备链表(这里是兼容SR-IOV的情况,确保先移除VF,然后再移除PF)
pci_stop_and_remove_bus_device #remove a PCI device and any children
->pci_stop_bus_device #停掉设备
->list_for_each_entry_safe_reverse #反向遍历设备链表,确保先移除VF,最后PF
pci_stop_bus_device #再去调用pci_stop_bus_device,这里就是一层层的去递归调用,从最底层的设备开始stop
->pci_stop_dev
->device_release_driver #停掉驱动
->device_release_driver_internal
->__device_release_driver
->driver_sysfs_remove #清理驱动的sysfs接口
->device_remove #去调用驱动的.remove方法,这个不在这展开,后面研究下virtio的驱动学习一下
->pci_proc_detach_device
->proc_remove #移除proc接口
->pci_remove_sysfs_dev_files #清理device的sysfs接口
->pci_dev_assign_added #移除设备的added状态,后续rescan的话可以重新探测到
->pci_remove_bus_device #移除设备
->list_for_each_entry_safe
pci_remove_bus_device #又是一次递归调用
->pci_remove_bus #移除bus相关的sysfs,调用bus的remove_bus方法,unregister bus上的device
->pci_destroy_dev
如果是safe remove,向pci配置空间写入禁用PCI_COMMAND_MASTER | PCI_COMMAND_SERR,打开PCI_COMMAND_INTX_DISABLE,不再允许设备发送request
->pci_unlock_rescan_remove #解锁总线
->pciehp_power_off_slot
->pcie_write_cmd #向设备写PCI_EXP_SLTCTL_PWR_OFF,关闭电源
->pciehp_set_indicators #设置indicator为PCI_EXP_SLTCTL_PWR_IND_OFF,指示此时电源已经关闭
->设置state为OFF_STATE
->pciehp_handle_presence_or_link_change #比DISABLE_SLOT事件优先级低(因为正经来说热拔需要先disable slot的),检测到PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC),也就是检测到presence detect,而且链路状态又发生了改变,,很明显,是发生了热插拔
->如果state是BLINKINGOFF_STATE,cancel delayed work(应该是处理attention button/软件通知之后,正在处理的过程中把设备拔出了?)
->如果state是ON_STATE,说明暴力热拔了,state设置为POWEROFF_STATE,然后去调用pciehp_disable_slot,第二个参数为SURPRISE_REMOVAL
->如果是BLINKINGON_STATE,也cancel delayed work
->如果state是OFF_STATE,设置state为POWERON_STATE,然后pciehp_enable_slot
->pciehp_enable_slot
->board_added #z这个就不再往下看了,估计就是remove_board的逆过程,添加sysfs proc,注册各种结构体注册设备之类的
以上,可以简单总结一下:
其实pciehp_ist处理热插拔就是相当于维护一个状态机。
一个热插流程为:插入pcie设备->触发presence detect->enable slot;或者attention button pressed/软件通知->enable slot
一个热拔流程为:attention button pressed/软件通知->disable slot->触发presence detect(但这时候这个presence detect应该没什么用了吧,就是如果attention button热拔完成之后再把设备从插槽拔除,当然也可以不拔)。如果直接暴力热拔的话,就是检测到presence detect,然后到pciehp_ist状态机发现是暴力热拔,然后去disable_slot(这时候是surprise remove)
注:
1.可以看到,在pciehp_handle_button_press事件处理中,ON/OFF的schedule_delayed_work是5秒后才执行的,也就是如果在着5秒内再次按下attention button的话,会取消上一次的操作。这也就是为什么如果已经处于BLINKING状态,再次检测到attention button pressed会恢复之前的状态了,过程是这样的,以热插设备为例,先按下attention button,表示这时候要进行热插了,走到pciehp_handle_button_press的ON_STATE的case那里,这时候把state切换为BLINKINGON_STATE,然后5秒钟后执行对应的button work,如果再5秒内有按下了attention button,会走到pciehp_handle_button_press的BLINKINGON_STATE(因为第一次按attention button的时候设置了状态)这里,这表示取消上一次的请求,于是把delayed work取消掉,然后把状态恢复为OFF_STATE(也就是热插之前的状态)。
2.在pciehp_handle_button_press那里的处理可以看出,如果是BLINKINGOFF_STATE,就应该去diable slot了,当这些完成后,用户拔出pcie设备(也可以不拔),触发presence detect;如果是BLINKINGON_STATE,就去请求presence detect中断。这也符合之前看到的通知式热插拔的处理过程:热插的时候,应该是先检测到presence detect,或者attention button pressed/软件通知,然后再去进行热插处理;热拔的时候,应该先attention button pressed/软件通知,然后发presence detect。
3.为什么在pciehp_unconfigure_device下有两次遍历设备操作?第一次是在pciehp_unconfigure_device中,第二次是在pci_stop_bus_device中。没太明白,猜测是不是pciehp_unconfigure_device下的遍历就是为了处理SR-IOV的情况,然后从反向遍历一直处理到VF,而pci_stop_bus_device就是真正处理stop行为的,而pci_stop_bus_device更抽象一点更通用一点,不论VF还是PF其他PCI设备都能处理,都执行一下递归处理?
virtio的 attach device和detach device的实现
大概了解了硬件pcie设备是怎么实现热插拔的,那就又来一个问题,虚拟化环境中的热插拔是怎么实现的?就比如virtio设备的热插拔?
比如virsh attach这个过程
#libvirt
cmdAttachDevice
->virDomainAttachDevice
->qemuDomainAttachDevice
->qemuDomainAttachDeviceFlags
->qemuDomainAttachDeviceLiveAndConfig
->virCheckFlags #这会检查flag,live是热插拔对当前活着的domain立即生效,config是持久化配置,估计是写到xml中,我主要想看libve热插拔怎么实现
->qemuDomainAttachDeviceLive #就看一下网卡吧
->qemuDomainAttachNetDevice
->qemuMonitorAddNetdev #发一个netdev_add的QMP,这里是只在虚机中创建一个网络设备
->qemuMonitorAddDevice #这里发一个device_add的QMP
#qemu
qmp_device_add
->qdev_device_add #这里获取dirver bus等
->qdev_new #创建device
->qdev_realize
->object_property_set_bool #这里会把realized设置为true,这个在device_class_init中已经使用object_class_property_add_bool为relized绑定了回调,把他设置为true,会去调用device_set_realized
->hotplug_handler_plug
->hdc->plug #调对应的回调,pci_bridge_dev_plug_cb
->shpc_device_plug_cb
->shpc_set_status #这里会设置presence detect这些
->shpc_interrupt_update #发中断到虚机中
#然后虚机就可以处理热插拔了,不过走的是shpc
后记
Downstream Port Containment:
DPC(Downstream Port Containment)是一种用于处理PCIe链路中错误情况的机制。当PCIe链路上的一个设备发送错误信号时,DPC机制允许系统采取相应的措施来隔离故障设备,以限制错误的传播范围,确保其他设备继续正常工作。DPC依赖于AER提供的错误信息来进行错误处理,是PCIe架构中重要的错误处理机制之一。
attention button和presence detect:
很明显,presence detect就是指PRSNT1#和PRSNT2#电平变化触发的中断,attention button也容易理解,就是先通知系统进行热插拔操作。
不过,不太理解的一点是,如果是热插怎么办?那不是presence detect比attention button事件更早嘛?噢,热插的话,其实就是这样的,先检测到presence detect,然后按下attention button才会触发后续的热插流程。那就有个疑问,没有attention button的pcie设备怎么做到的热插呢?看上面的过程,如果是热插的attention button pressed,其实也就是请求一个presence detect,那么就需要知道attention button请求的PDC和pcie设备插入产生的PDC处理有什么不同嘛?我的意思是,pcie设备热插的时候肯定需要先插入插槽,那这时候肯定就已经走PDC去进入热插流程了,为什么还需要按下attention button?看了下pciehp_handle_presence_or_link_change这块,其实对attention button和直接插入的PDC处理过程也就一点差异:attention button pressed会先把状态改为BLINKING,这时候会进行一些检查之类的,但是无论是attention button的PDC还是直接插入插槽触发的PDC,都会走pciehp_enable_slot去执行热插流程。
可能,我觉得是这样。热插并不只有直接把pcie设备直接查到插槽上这一种方式。如果是通过attention button热拔掉的设备呢,这时候应该本来就可以还插在插槽上,这时候再按下attention button进行的热插就是走上面说的attention button pressed的PDC了,也就是会多一个BLINKING状态,加一些检查之类的。
所以,其实我觉得怎么区分暴力热插拔和通知式热插拔呢(我没见过带attention button的pcie设备)?只要是直接插入插槽的,那就是暴力热插,不按attention button或者软件通知的直接把设备拔出插槽的就是暴力热拔;什么是通知式的呢?这个pcie设备本来就插在插槽上了,通过软件通知或者attention button pressed让他再重新被系统使用(这个过程不涉及物理上的插入插槽操作),或者从系统中除去。
参考
https://blog.csdn.net/qq_22745707/article/details/144105800
https://blog.51cto.com/u_56701/10914238
https://zhuanlan.zhihu.com/p/691014216
后后记
2025.4.4清明假期,晚上开始看这块内容,第二天又看了一天,眼睛好难受,还是只了解了一下大概过程,细节还不甚清楚,好几处地方还不太理解
主要还是对pcie设备模型这块不太了解吧,后面需要再加强这块的学习
太深了,学吧,学无止境
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 857879363@qq.com