当前位置: 首页>前端>正文

PCI Express电源选项关系 知乎 pci电源管理

一 ACPI模式下设备的休眠唤醒

ACPI规范假设设备可以处于四个电源状态之一,分别标记为D0,D1,D2和D3,大致对应于本机PCI PM D0-D3状态(尽管D3hot和D3cold之间的差异并未考虑在内) ACPI)。此外,对于设备的每个电源状态,必须启用一组电源才能使设备进入该状态。这些功率资源借助自己的控制方法_ON和_OFF进行控制(即启用或禁用),必须分别为它们定义。

为了使设备进入ACPI电源状态Dx(其中x是介于0和3之间的数字,包括x),内核应:

(1)使用其_ON控制方法启用处于该状态的设备所需的电源

(2)执行为设备定义的_PSx控制方法。

除此之外,如果设备将进入低功耗状态(D1-D3),并且从该状态生成唤醒信号,则_DSW(或_PSW,由ACPI 3.0替换为_DSW)控制方法为此定义的对象必须在_PSx之前执行。应禁用目标电源状态下该设备不需要的功率资源,而其他任何设备都不再需要的功率资源(通过执行其_OFF控制方法)被禁用。如果设备的当前电源状态为D3,则只能通过这种方式将其置于D0中。

但是,在系统范围内转换到睡眠状态或返回工作状态时,设备的电源状态经常会更改。ACPI定义了四个系统睡眠状态S1,S2,S3和S4,并将系统工作状态表示为S0。通常,目标系统的睡眠(或工作)状态确定设备可以进入的最高功率(最低数量)状态,并且应该假定内核通过执行设备的_SxD控制方法来获取此信息(其中x是介于0和4(含0和4)。如果要求设备将系统从目标睡眠状态唤醒,则可以进入的最低功耗(最高数量)状态还取决于系统的目标状态。然后,内核应该使用设备的_SxW控制方法来获取该状态的编号。

 

1、ACPI中的对象

(1) _SxD方法:提供设备的s -状态到d -状态的映射,将本设备在Sx系统睡眠状态下支持的最高功率d状态传送给OSPM。

(2)_SxW方法:将本设备所支持的在Sx系统睡眠状态中能够唤醒系统的最低功率d状态传送给OSPM。

(3) _PSx方法:将指定设备置为Dx状态。

(4) _PRx方法:设备的Dx下,电源资源列表

(5) _DSW方法:启用或禁用休眠唤醒

(6) _PRW方法:设备所依赖的用于唤醒的电源资源列表,_PRW只适用于那些能够将系统从休眠状态唤醒的设备。

(7)如果存在任何控制电源的ACPI对象(_PSx或_PRx,其中x =0、1、2或3),则必须提供将设备设置为D0和D3设备状态(至少)的方法。

(8)如改变设备电源状态,则需唤醒系统的睡眠状态

2、内核流程

  1. 名称空间设备对象初始化

在扫描pci设备树前,acpi遍历了名称空间,检测DSDT中关于pci设备的对象的描述以及设备节点下对象及控制方法的解析。当检测到pci对象后,调用acpi_bus_check_add函数来初始化。

->acpi_bus_type_and_status函数获取acpi对象的类型:

如果为处理器对象,则评估_STA,如不存在_STA对象,则假设设备正常。

如果为设备对象,且为存在SPCR表支持的串口设备,则不做初始化。

如为电源对象,则调用acpi_add_power_resource。

->acpi_add_single_object函数

  1. acpi_add_single_object
  1. acpi_init_device_object,初始化device结构,包括对象的类型,对象句柄,对象当前状态等。

对于ACPI_BUS_TYPE_DEVICE,获取状态被延迟到这里,这样我们就可以调用acpi_bus_get_status()并使用它的异常处理。注意,这必须在get power-/wakeup_dev-flags调用之前完成。

2) acpi_bus_get_power_flags

a)如存在_PS0或_PR0,则存在电源管理:device->flags.power_manageable = 1;

b) _PSC存在,可获取电源状态:device->power.flags.explicit_get 有效;

c) _IRC存在,则device->power.flags.inrush_current 有效;

注:acpi_bus_init_power_state设置D3hot相关flag及电源资源

d) _DSW存在,device->power.flags.dsw_present有效

e) 评估_PR0获取电源资源列表,device->power.flags.power_resources = 1;如电源资源描述存在,则device->power.states[state]->flags.valid有效。评估_PS0,ps->flags.explicit_set = 1;

f) 设置默认的D0及D3cold电源状态,调用acpi_bus_init_power函数,获取当前设备电源状态(推断或评估_PSC)。执行设备电源资源列表中的_ON以及_PSx控制方法。启用设备获取当前状态下的电源资源。

  1. acpi_bus_get_wakeup_device_flags分析

1) acpi_bus_extract_wakeup_device_power_package

评估_PRW方法,acpi_bus_extract_wakeup_device_power_package将相应的资源及附加信息记录到设备的wakeup结构中。如下为返回信息说明:

Package {
EventInfo // Integer or Package
DeepestSleepState // Integer
PowerResource [0] // Reference
. . .
PowerResource [n] // Reference
}

支持四种类型的通用事件:

•由FADT中描述的GPE块定义的GPE。

•由GPE块设备定义的GPE。

•由GPIO控制器设备的_AEI对象定义的GPIO信号事件

•由通用事件设备(GED)的_CRS对象定义的中断信号事件

这四种事件类型由返回包中EventInfo对象的类型来区分。

对于基于fadt的GPEs, EventInfo是一个包含位索引的整数。

对于基于块设备的GPEs, EventInfo是一个包,包含对父块设备的引用和一个包含位索引的整数。方法中包含对GPIO控制器设备的引用和包含事件索引的整数

_AEI对象(从零开始)。对于中断信号事件,EventInfo是一个包,其中包含对GED的引用和一个整数,该整数包含_CRS对象中事件的索引(从零开始)。

DeepestSleepState是一个整数,它包含可以在提供唤醒功能的同时输入的最小功耗系统睡眠状态。

PowerResource 0-n是对所需power resource对象的引用。

2) acpi_wakeup_gpe_init

在这个函数中,如果设备对象为固定事件中电源按钮事件、睡眠按钮事件以及翻盖事件,则标记相应的gpe_event_info->flags |= ACPI_GPE_CAN_WAKE;并且使能设备可唤醒系统。其他设备对象则调用acpi_setup_gpe_for_wake将对应GPE事件标记为具有唤醒系统的能力。这个函数用于主机执行系统表中的_PRW方法(唤醒的电源资源)。(每个_PRW出现在一个设备对象(wake_device)之下,并包含与wake_device关联的唤醒GPE的信息)acpi_setup_gpe_for_wake设备成功则使能device->wakeup.flags.valid标志。

  1. 调用acpi_device_sleep_wake函数,禁用当前设备唤醒系统的能力。评估acpi对象_DSW或_PSW.
  1. acpi_device_add

将遍历到的名称空间的设备连接到它的同伴以及父节点上。

 

(2) PCI子系统suspend

在系统发生休眠时,pci子系统调用pci_pm_suspend_noirq来实现总线及设备级的休眠。在这个函数中,首先获取设备相关联的驱动注册的电源管理回调。然后调用相关设备注册到系统中的suspend_noirq函数,执行设备级相关的suspend操作。在设备及suspend函数中,最终会调用pci_prepare_to_sleep函数,在ACPI模式下,通过底层注册的接口实现设备级的休眠。

  1.  device_may_wakeup

根据power.can_wakeup及dev->power.wakeup标志(在前面遍历名称空间时初始化),返回当前设备是否支持唤醒系统。

  1.  pci_target_state

首先判断在当前设备在ACPI模式下是否由相关设备电源管理的描述即device->flags.power_manageable标志位是否有效(前面遍历名称空间已初始化)。

  1. 如存在,则调用platform_pci_choose_state函数接口,根据系统当前状态,根据DSDT中描述的电源管理对象(_SxD,SxW,_PRW)。返回一个设备合适的状态并且平台如果支持则使能从当前获取的电源状态唤醒(设备唤醒)。
  2. 不存在,且设备不存在电源管理寄存器集(没有pm capbility实现)。则返回全功能状态D0。
  3. 不存在、存在电源管理寄存器集且设备可唤醒系统。则如设备支持PME。返回相应设备状态。

2-1 platform_pci_choose_state

_SxD返回在S“x”状态中支持的最高功率(最低d状态数)的d状态。

如果设备没有_PRW(用于唤醒的电力资源)控制方法。则支持系统从S“x”唤醒,那么操作系统可以自由地选择比从_SxD返回值更低的电源(更高的数字D-state)。

如果在S“x”处启用了_PRW,则操作系统不能选择低于_SxD的功率——除非设备具有_SxW方法,该方法指定设备可以输入的最低功率(最高d状态数),同时仍然能够唤醒系统。

首先判断是否禁用了D3cold状态。调用关系如下所示:acpi_pm_device_sleep_state->acpi_pm_device_sleep_state->acpi_dev_pm_get_state.

acpi_dev_pm_get_state分析:(获取_SxD和_SxW值)

 *当系统处于@target_state表示的状态时,查找设备可以处于的最低功耗和最高功耗的ACPI设备功耗状态。将表示这些状态的整数分别存储在由@d_max_p和@d_min_p指向的内存位置中。在使用这个函数之前,调用者必须确保@dev和@adev是有效的指针,并且@adev实际上对应于@dev。

如果系统状态为S0,则设备可以处于的最低功率状态为D3cold,除非设备具有_S0W,并且假定该设备具有唤醒信号,在这种情况下,必须使用_S0W的返回值作为设备可用的最低功率状态。

如果存在_SxD方法,这个方法返回我们可以用于对应s状态的最小d状态(最高功率状态)。否则,最小d状态为D0 (ACPI 3.x)。

如果系统状态>S0(当前为省电模式),则评估设备下对应的SxD(x对应系统状态S0~S5)。

需要处理遗留系统,其中D3hot和D3cold是相同的,并且在这两种情况下都返回数值3,所以如果D3hot不是有效状态,则返回D3cold。

如果_PRW标识可以将系统从目标睡眠状态唤醒,那么_SxD返回的设备最高功率(最小d)足够了(如果设置了唤醒,我们假设有一个唤醒感知的驱动程序)。尽管如此,如果_SxW存在(ACPI 3.x),它应该返回能够唤醒系统的最大d状态(支持设备可唤醒系统的最低功耗)。_S0W可能也是有效的。

如果系统中存在_SxW,评估这个控制方法。获取支持设备可唤醒系统的最低功耗。

  1. pci_enable_wake 使能对应设备的唤醒功能

a) PCI(相对于PCIe) PME要求设备正确连接其PME#电路。并不是所有的硬件供应商都这样做,所以PME永远不会交付,设备仍然处于休眠状态。解决这个问题的最简单的方法是周期遍历suspend设备的列表,检查是否设置PME标志。如果是,则唤醒。

虽然PCIe使用带内PME消息而不是PME#行来报告PME,但PME在现实中并不适用于某些PCIe设备。例如,有些设备设置了它们的PME状态位,但实际上并不发送PME消息;有一种PCI Express根端口,当它们从下面的设备接收PME消息时,不需要触发中断。所以PME轮询也适用于PCIe设备。

pci_pme_active创建设备的PME轮询工作队列。

b)platform_pci_set_wakeup->acpi_pci_wakeup

判断adev->wakeup.flags.valid是否有效。调用__acpi_device_wakeup_enable函数

->acpi_enable_wakeup_device_power:如果由电源资源描述。则执行_ON方法,然后执行_DSW方法使能对应设备的唤醒功能。

->acpi_enable_gpe:确保我们有一个有效的GPE编号,并且有某种处理GPE的方法(处理程序或GPE方法)。换句话说,如果无法处理有效的GPE,我们将不允许启用它。所以首先获取gpe_number所对应的gpe_event_info。

->acpi_ev_add_gpe_reference 添加一个GPE的索引

->acpi_ev_update_gpe_enable_mask初始化

gpe_register_info->enable_for_run

最后调用acpi_ev_enable_gpe使能GPE。

  1. pci_set_power_state
  2. pci_raw_set_power_state

读相应设备的电源管理寄存器pmcsr字段,根据前面获取的状态,更改电源状态,写入配置空间。1>设备当前状态和要设置状态相同,2>不支持pm capability,3>要设置状态错误,4>设备状态转换不合理或不支持。则返回

配置电源管理寄存器,使pci设备进入相应的电源状态。根据状态对应的延时。更改相应pcie设备的电源状态。

D0-3之间的切换,是由软件对PM CAPABILITY REGISTERS中的PMCSR 的POWER STATE写入相应状态值

  1. __pci_complete_power_transition

1>platform_pci_set_power_state

判断是否存在电源管理对象,存在则调用platform_pci_set_power_state,获取设备在DSDT中的设备句柄(节点地址)。判断设备节点下是否存在_EJ0(断开设备的所有电源)。有则返回。然后判断电源状态,除了设备状态为D3cold时,检测设备的PM Qos标志,为PM_QOS_FLAGS_ALL时,退出设置电源状态。否则执行acpi_bus_set_power->acpi_device_set_power设置设备的电源状态。

在acpi_device_set_power函数中,首先检测要设置电源状态及参数是否正常。 (如果在设备节点下,存在电源资源的描述。则在电源资源列表中执行所有需要的_ON控制方法。这样设备在转换时就不会断电。然后,执行当前列表中使用的所有电源资源的_OFF控制方法)

如果当前设备状态大于D0,即为省电模式,调用acpi_dev_pm_explicit_set执行DSDT中相应的控制方法_PSx。将设备设置为指定电源状态。否则执行电源资源中断的_ON方法。

2> pci_update_current_state

如上述执行完成,则调用pci_update_current_state函数,这个函数首先获取当前设备电源状态(断言或执行_PSC方法),更新dev->current_state。

  1. 如pci_set_power_state函数执行失败,则禁用设备的唤醒功能。

 

 

(3) PCI子系统resume

在系统发生唤醒时,pci子系统调用pci_pm_resume_noirq来实现总线及设备级的唤醒。在这个函数中,首先调用在ACPI模式通过底层注册的函数实现设备的唤醒功能。然后清除PME相关状态。最后获取设备相关联的驱动注册的电源管理回调。然后调用相关设备注册到系统中的resume_noirq函数。

  1. pci_pm_default_resume_early

a)pci_power_up

调用__pci_start_power_transition函数,将设备设置为D0状态。并更新设备电源状态dev->current_state。

调用pci_raw_set_power_state读设备配置空间,更改相应位使设备恢复的D0状态。

调用pci_update_current_state,更改当前设备的dev->current_state。

b)pci_restore_state

恢复睡眠时保存的pci设备的状态。

c)pci_pme_restore

恢复pcie设备电源管理寄存器pmcsr的pme enable位的状态。

2)pci_legacy_resume_early

判断是否是传统的电源管理模式。如果是调用pci_legacy_resume_early函数来唤醒设备。

打开端口,即使没有每个端口的切换。HC会在设置之前报告连接事件。然而,hub_wq会忽略roothub事件,直到注册了roothub。

3)pcie_pme_root_status_cleanup

清除PME位,禁用设备发送PME,产生唤醒的SCI中断。

最后调用设备相关的注册到系统的resume函数(pm->resume_noirq函数)。执行设备级相关的恢复流程。

 

  • 唤醒事件相关

1、SCI中断以及通用事件(GPE)的处理

(1) 数据结构

GPE Block结构:struct acpi_gpe_block_info *gpe_block,它是GPE的响应核心,在初始化ACPI驱动的阶段将会由acpi_ev_create_gpe_block这个函数创建这个数据结构,扫描名字空间(Namespace);在名字空间中通常会有专门的全局对象节点针对GPEx_STS寄存器的各个位具体执行操作节点进行描述如下例:_GPE用来表示GPE寄存器,以及相应的需要通知OSPM处理动作,例如:

_GPE
Method(_L01) { // Update device
Sleep(250) // Mechanical Delay 
Notify(CDRM, 1)

_GPE表示的是当前的通用事件寄存器的ASL描述,而Method(_L01)当前的通用寄存器的某个位的控制方法,_ L表示这个位的触发状态为电平触发,01表示的通用寄存器的01位。

在Linux中通过在初始化名字空间的时候使用acpi_ns_walk_namespace函数对当前的_GPE名字空间进行扫描:

... ...
status = acpi_ns_walk_namespace (ACPI_TYPE_METHOD, gpe_device,
ACPI_UINT32_MAX, ACPI_NS_WALK_NO_UNLOCK, acpi_ev_save_method_info,
gpe_block, NULL);
... ...
  1. 回调函数acpi_ev_save_method_info在扫描名字空间的时候被调用,按照名字空间中针对GPE的描述,找到当前需要实现响应的GPE节点初始化gpe_block中有关GPE寄存器状态位的结构even_info。
  2. acpi_ev_save_method_info回调函数找到_GPE通用事件寄存器的描述节点,并为在名字空间中通用事件寄存器的每个位安排相应的响应。
  3. 在acpi_gpe_block_info中有一个字段event_info(事件消息描述结构)包含了每个位所对应的名字空间中所执行的方法节点(method_node字段),如下:
struct acpi_gpe_event_info
{
struct acpi_namespace_node   *method_node; 
/* 当前GPE事件所需要在执行的名字空间的方法节点,如上面例中的Method(_L01)*/
acpi_gpe_handler                handler;     
/* 执行的上层程序(驱动程序层)句柄
例如某些时候在PCI设备进行热拔插的时候需要在插入后,对资源进行重新分配
例如访问ECDT表(ACPI嵌入式控制器专用),重新定义PCI配置空间并不需要再重新定义Notify的操作就直接通过这个句柄直接执行.
例如某些ACPI嵌入式控制器的驱动中就提供了acpi_install_gpe_handler的接口来安装这个句柄*/
void                        *context;           /* 代入的参数 */
struct acpi_gpe_register_info   
*register_info; /*指向当前GPE寄存器组 */
u8                           flags;          
/*响应的电平(边沿触发或者是电平触发)*/
u8                           bit_mask;          /* GPEx_STS的掩码 */
};
例1-4 GPE寄存器在Linux中的数据描述结构

通过acpi_gpe_event_info这个数据结构就把硬件上的状态寄存器和名字空间中的方法节点对应起来。

 

(2) GPE的事件响应机制

事件响应条件:

(1)当事件寄存器有某个位发生变化,

(2)且相应的使能寄存器的位表示这个位使能(使能寄存器在event_info的GPE寄存器组字段 enable_address寄存器表示),那么表示当前的事件响应会有效,然后引发SCI中断,任何ACPI中断都会引发SCI中断,它一般是一个普通的CPU电平中断低电平有效。

在ACPI模块中函数acpi_ev_handler_initialize对SCI的中断句柄进行初始化,通常在Linux的ACPI驱动中有每个中断一般会对应两个GPE Block(对应于两个不同的寄存器GPE0_STS和GPE1_STS),另外还包括gpe_block_list_head和acpi_gbl_gpe_xrupt_list_head队列负责处理SMP的情况,

在linux的ACPI处理机制中acpi_ev_sci_xrupt_handler函数就是SCI中断处理的句柄,所有GPE共享这个SCI中断句柄。

在acpi_ev_gpe_detect函数中扫描acpi_gbl_gpe_xrupt_list_head全局队列上挂的GPE Block检查事件状态,并且分发到相关的事件的处理进程,并根据当前使能状态寄存器来确定是否响应当前事件,然后调用acpi_os_queue_for_execution这个ACPI OS层的执行过程把当前的所需要执行的进程acpi_ev_asynch_execute_gpe_method放入当前的OS中的执行队列中异步执行。

执行过程 : (1)首先会检查当前寄存器合法性 (2)然后执行GPE的对应控制方法节点并清零GPEx_STS (3)调用acpi_ns_evaluate_by_handle执行。

(3) Notify的方法执行/初始化过程

当一个GPE事件发生之后,所调用的方法节点通常会调用"NOFITY"操作符,通知OSPM进行处理,通常一个ASL的通告操作表示如下:

Notify(\_SB.PCI0.P2P2,0)

\_SB.PCI0.P2P2表示当Hot Plug阶段的该设备接收响应事件,0表示ACPI向OSPM通告消息BUS Check通告, 通知PCI总线驱动层进行枚举。

在热拔插中要用到以下的几个系统级别的通告消息如下:

(注:0x80以上是针对具体设备对象通告,例如电池,温度控制,热拔插等设备)

0 --- 总线检查:设备对象出现,通知OSPM,完成"Plug and Play"的枚举操作。当收到通知时,OSPM执行这个操作,在热拔插的时候,由ACPI AML通过Notify的方式通知该值到OSPM,

1 --- 设备检查:用于通知OSPM,设备或出现或消失。如果设备出现,OSPM将从设备节点的父节点"重新枚举(re-enumerate)"。如果设备拔出,OSPM将使设备的状态定为无效。OSPM可以优化重新枚举的过程。

2 --- 设备唤醒:用于通知OSPM设备发出了它的睡眠事件信号,OSPM需要通知OSPM的本地设备驱动,此情况仅用于支持_PRW的设备。

3 --- 拔出要求:用以通知OSPM设备应被弹出,同时OSPM需要执行"Plug and Play 弹出操作",与此同时OSPM将运行_Ejx方法。

6 --- 总线模式错配:用以通知OSPM设备被插入一个非当前操作模式的槽或背板中。例如:当用户试图将一个PCI设备热插入一个运行在PCI-X模式下的总线的槽中。

7 --- 电源故障:用以通知OSPM,设备由于电源故障不能从D3状态下转出。

(1)执行对象节点方法和前面的执行型的ACPI命令的处理过程相同。

(2)Notify操作,这个操作是底层(ACPI层)向上层(驱动层)通告当前消息

它和普通的命令型操作没有本质上的区别,但由于向操作系统层通告消息,所以要调用一些ACPI OS部分的API;和上面介绍的_EJ0方法一样,

  1. 会根据操作符的描述集合op_info执行句柄acpi_ex_opcode_2A_0T_0R
  2. 进一步根据NOTIFY的通告值分发到处理过程,相关的处理过程是异步执行的。
  3. 最终会调用到acpi_os_queue_for_execution这个ACPI OS层的执行接口,它在Linux中执行方式就是把需要执行的任务(而acpi_ev_notify_ dispatch调用NOTIFY节点对象的实际执行者)插入任务队列中,不过在某些采用实时的Linux系统场合,有一些人会把它插入到立即队列中(Immediate Task Queue),两者实现的效果是相同的。

对于一个NOTIFY节点来说,其节点对象类型是:

struct acpi_object_notify_common_notify common_notify类型,通常它的内部关键的字段如下表示:

...
union acpi_operand_object  *system_notify;     /* 系统的消息通告 */
union acpi_operand_object  *device_notify;     /* 设备消息通告*/
union acpi_operand_object  *handler;           /* 地址空间中的中的执行句柄 */
...

acpi_operand_object这个结构内都包含一个代表消息执行句柄的acpi_object_notify_handler结构,其中的handler,node,context字段分别表示针对当前通告的执行句柄,当前执行句柄所对应设备的名字空间节点,以及执行句柄带入的参数;

common_notify结构会在驱动程序初始化时候安装acpi_object_notify_handler中对应事件操作句柄,操作系统驱动程序会调用ACPI提供的接口acpi_install_notify_handler完成这个的动作,使用这个接口可以屏蔽掉一些作为驱动设计者无须知道的硬件和ACPI层的细节:

在下面是该函数在Linux中的原型:

acpi_status
acpi_install_notify_handler (
    acpi_handle                     device,
    u32                             handler_type,
    acpi_notify_handler             handler,
    void                            *context
)

1. 第一个参数acpi_handle是需要接收通告的设备句柄;

2. 第二个参数handler_type表示当前设备可以处理的消息号类型,分成设备类型和系统类型两种;

3. acpi_notify_handler表示执行消息的回调函数;

4. 回调函数的带入参数。

 

2、内核流程

(1) 系统模式

start_kernel->acpi_subsystem_init->acpi_enable_subsystem(~ACPI_NO_ACPI_ENABLE) 如果acpi_gbl_FADT.smi_command为0,则代表不支持系统的模式转变,只能为acpi模式。获取当前系统模式,通过读取FADT中的pm1 sci_en寄存器判断。

(2) ACPI子系统初始化

acpi_bus_init->acpi_enable_subsystem(ACPI_NO_ACPI_ENABLE)

  1. 映射FACS表
  2. 初始化ACPI事件处理,包括固定事件及GPE事件
->acpi_ev_initialize_events
->acpi_ev_fixed_event_initialize固定事件初始化

初始化固定时间寄存器,包括ACPI_EVENT_PMTIMER、ACPI_EVENT_GLOBAL、ACPI_EVENT_POWER_BUTTON、ACPI_EVENT_SLEEP_BUTTON、ACPI_EVENT_RTC。

->acpi_ev_gpe_initialize
->acpi_ev_create_gpe_block

创建struct acpi_gpe_block_info gpe_block,根据FADT中对GPE的描述,初始化这个GPE block。

->acpi_ev_create_gpe_info_blocks

注册register_info和event_info,首先申请gpe_register_info和gpe_event_info结构,初始化gpe_block->register_info及gpe_block->event_info。初始化GPE寄存器和事件结构。(在给定的GPE硬件块中有两个独立的GPE寄存器集,状态寄存器占用了前半部分,而enable寄存器占用了后半部分。)将事件结构一一对应寄存器信息。

->acpi_ev_install_gpe_block安装新的支持互斥的GPE块到链表。

->acpi_ev_get_gpe_xrupt_block获取或创建一个GPE中断。

(1) 遍历全局链表acpi_gbl_gpe_xrupt_list_head,查找gpe sci中断是否存在。

(2) 未找到则创建一个新的中断描述符,填充系统中断号,将创建的中断描述符赋值给acpi_gbl_gpe_xrupt_list_head。

将acpi_gbl_gpe_xrupt_list_head->gpe_block_list_head = gpe_block;

gpe_block->xrupt_block = gpe_xrupt_block;
->acpi_ns_walk_namespace

遍历名称空间,查找Lxx或者Exx控制方法,将找到的控制方法通过acpi_ev_match_gpe_method函数对象中提取名称和GPE类型,保存此信息到前面初始化完成的gpe_event_info结构。以便在GPE分派期间快速查找。

(3)初始化SCI中断处理

->acpi_ev_install_xrupt_handlers

安装SCI中断处理函数及全局锁初始化。

->acpi_ev_install_sci_handler

注:acpi_gsi_to_irq将ACPI sci中断注册。(mips架构)

request_irq注册中断服务:当发生FADT中记录的SCI中断号事件时,调用中断处理函数acpi_ev_sci_xrupt_handler。

中断处理函数分析:

1)首先读取PM1_STATUS及PM1_ENABLE寄存器,遍历所有固定事件,当发生中断的对应STS位的EN位都使能,则调用acpi_ev_fixed_event_dispatch函数,清中断,判断固定事件是否注册了相应的中断处理事件(在全局固定事件处理函数数组acpi_gbl_fixed_event_handlers,通过acpi_install_fixed_event_handler注册),如存在则调用相应的中断处理函数。

2)如为GPE事件

a)获取gpe_block,遍历gpe_block,如对应的GPE寄存器标志没使能(enable_for_run和enable_for_wake都为0,对应GPE事件没有调用acpi_enable_gpe函数使能),则跳过执行中断处理;

b)如使能,则调用acpi_ev_detect_gpe函数。检测并向函数(如EC)或方法(如_Lxx/_Exx)处理程序发送通用事件。首先读取对应的GPE寄存器状态和使能位。如使能,则判断gpe_event_info->flags(见linux-4.19中include/acpi/actypes.h line750)。

1> 如为raw handle(EC的Qxx方法),调用相应的GPE事件处理函数.(ec_install_handlers->acpi_install_gpe_raw_handler->acpi_ev_install_gpe_handler)

2> 否则调用acpi_ev_gpe_dispatch函数,处理过程禁用GPE,如果既没有处理程序也没有方法,则禁用GPE。如果边缘触发,现在清除GPE状态位。请注意,在服务GPE之后,电平触发的事件才被清除。

将GPE分派给已安装的处理程序或与此GPE关联的控制方法(_Lxx或_Exx)。如果存在处理程序,我们将调用它,并且不尝试运行该方法。如果既没有处理程序也没有方法,则禁用GPE。acpi_os_execute调用回调函数acpi_ev_asynch_execute_gpe_method:GPE控件方法的实际执行

在acpi_ev_asynch_execute_gpe_method函数中:

当为Notify时,将DEVICE_WAKE通知发送给适当的处理程序。首先检测Notify类型,然后通过acpi_os_execute加入执行队列,调用回调acpi_ev_notify_dispatch,这个函数中,执行相应已安装的notify方法(1、检测acpi_gbl_global_notify[handler_list_id]是否已安装2、检测obj_desc->common_notify.notify_list[handler_list_id]方法)。

当为控制方法,及Lxx或Exx,调用acpi_ns_evaluate函数执行控制方法。

最后调用acpi_os_execute将回调函数加入执行队列,回调函数acpi_ev_asynch_enable_gpe使能GPE。

(3) acpi_setup_gpe_for_wake

当gpe_device为NULL,则使用的为Fadt中定义的GPE事件。

如果没有此GPE的方法或处理程序,则每当此GPE触发时,将通知wake_device。这就是所谓的“隐式通知”。注意:GPE被认为是电平触发的(为了windows兼容性)。

这是该GPE上第一个用于隐式通知的设备。只需在这里设置标志,并在下面输入NOTIFY块。

在GPE块初始化期间添加了对这个GPE的引用,所以现在删除它以防止永久启用GPE,并清除它的ACPI_GPE_AUTO_ENABLED标志。


https://www.xamrdz.com/web/2ey1962318.html

相关文章: