1. 一次被动调度引发的硬核排查:StartTimeSlicePassive到底卡在哪了
先交代下背景。最近在调一块板子的PCIe设备枚举问题,现象很典型:操作系统启动后,PCIe总线上有设备,但对应的ACPI设备节点(PNP0C02或特定HID)就是enumerate不上来。于是翻起了DSDT,顺着命名空间一路挖,最后追到了 AML 解释器里一个叫 StartTimeSlicePassive 的函数,并且在这里面看到了对节点 Device(P2P0) 的子节点 Device(S1F0) 执行 _ADR 方法的过程。
这名字听起来很底层,确实也很底层。它不是ACPI规范里直接暴露给OS的接口,而是AML解释器(比如ACPICA或内核自带解释器)在实现异步控制方法执行时的一个内部调度函数。简单说,当系统里同时有多个AML方法需要跑,或者某个 _Lxx、_Exx、_Qxx 事件触发了控制方法排队,解释器不能让一个方法独占CPU太久,它会把执行过程切成时间片(TimeSlice),用被动分发(Passive)的机制挨个执行。StartTimeSlicePassive 就是启动这个被动时间片分发的入口。
在这个时间片里,解释器会遍历当前需要执行的方法队列,涉及某个设备节点的时候就准备上下文、传入参数、执行AML字节码。而 Device(S1F0) 的 _ADR 方法正是在这种被动执行上下文中被调用的。听起来有点绕?我画个实际场景你就明白了。
假设你的板子是标准PCIe拓扑:CPU出总线,接一个Root Complex,下面有Root Port(对应ACPI里的 Device(P2P0)),Root Port下面挂了一个NVMe或者PCIe Switch,再往下是Endpoint。OSPM(ACPI驱动)在启动早期会做两件事:一边走PCIe配置空间的枚举,读出总线上每个设备的Bus/Device/Function;另一边解析DSDT里的ACPI设备树,对每个 Device 节点调用 _ADR,拿它返回的地址去和PCIe枚举结果“配对”。
_ADR 就是干这个用的。它不返回什么复杂结构,就是一个整数:高16位是Device Number,低16位是Function Number。比如一个设备在PCI总线上是Bus 0, Device 1, Function 0,那 _ADR 应该返回 0x00010000。如果 _ADR 写错了或者执行时机不对,OSPM就匹配不上,轻则设备无法关联ACPI控制方法(比如无法做电源管理),重则直接导致设备不被枚举。
我这次遇到的就是 Device(S1F0) 的 _ADR 执行结果和PCIe配置空间读到的实际Device Number不一致,导致设备节点虽然在ACPI树里存在,但系统始终不认为它是某个PCIe设备。排查到 StartTimeSlicePassive 里面的时候,才发现问题比想象中复杂——不是 _ADR 返回值算错了,而是这个节点的 _ADR 在被动调度的执行路径上被“延迟”了,等真正执行完成的时候,设备枚举的匹配窗口已经过了。 这属于典型的AML异步执行时序问题,光看DSDT代码根本发现不了,必须进解释器内部抓执行链。这也正是我写这篇文章的原因:从 StartTimeSlicePassive 这个入口进去,完整拆一遍节点遍历和 _ADR 执行的底层逻辑,把坑说透。
如果你也正在被ACPI设备枚举、_ADR 匹配、AML解释器时序这类问题折磨,或者你是做固件/BIOS/BSP的人,想理解命名空间和PCI枚举之间的耦合关系,这篇文章值得你花十分钟读一下。我会把节点树、地址编码、被动调度机制、踩坑经验一条条捋清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从Device(P2P0)到Device(S1F0):先搞清楚这棵树是怎么长的
2.1 P2P0和S1F0在命名空间里的“父子”关系
ACPI命名空间(Namespace)本质是一棵有层次结构的树,树的节点包括 Device、Scope、Processor、ThermalZone 等。Device(P2P0) 和 Device(S1F0) 不是两个并列的兄弟节点,而是父子节点——S1F0 定义在 P2P0 的作用域之内。
实际DSDT片段大概长这样:
asl复制Device(P2P0) {
Name(_ADR, 0x00040000) // PCIe Root Port, Device 4 Function 0
Name(_STA, 0x0F)
Method(_PRW, 0) {
Return (GPRW(0x09, 0x04))
}
Device(S1F0) {
Name(_ADR, 0x00010000) // 设备号1,功能号0
Name(_HID, EISAID("PNP0C02"))
Name(_STR, Unicode("Storage Slot 1 Function 0"))
...
}
}
P2P0 这个名字通常是厂商自己起的,代表PCI-to-PCI Bridge 0,也就是PCIe Root Port。S1F0 则是它的下游设备,可能是Slot 1的Function 0。P2P0 下的 _ADR 告诉OS这个Root Port本身在总线上的位置;S1F0 的 _ADR 则告诉OS下游设备在次级总线上的位置。
这里有个知识点:ACPI里 _ADR 的含义取决于设备类型,对于PCI设备,_ADR固定表示 Device Number 和 Function Number。 它不包含总线号,因为总线号由PCI枚举动态分配,而ACPI命名空间层级本身就隐含了总线拓扑关系。
2.2 为什么父节点P2P0的_ADR要先执行
在 StartTimeSlicePassive 处理节点的时候,解释器不是随机挑一个节点执行的,它会按命名空间的深度优先顺序来处理。处理 S1F0 之前,必须先处理它的祖先节点 P2P0。
这不是解释器闲着无聊,而是OSPM的设备枚举逻辑有依赖关系:
- 先读取
P2P0的_ADR,确定Root Port在总线0上的位置。 - 初始化Root Port,配置其PCI配置空间中的Bus Number寄存器,建立下游总线(Bus 1)。
- 然后才遍历
P2P0的子节点S1F0,尝试在Bus 1上找到Device 1 Function 0对应的PCI设备。
所以如果 StartTimeSlicePassive 在遍历到某个节点时,它的父节点还没有完成 _ADR 读取和总线初始化,那么子节点的执行顺序就会被打乱。这种树级依赖关系在ACPI源文件里看不太出来,但在被动调度执行队列里非常关键。
2.3 节点遍历的内部表示
在ACPICA内部,每个命名空间节点是一个 ACPI_NAMESPACE_NODE 结构体,大致包含:
| 字段 | 作用 |
|---|---|
Name |
节点名称(如 P2P0) |
Type |
节点类型(Device/Method/Scope等) |
Flags |
节点状态标志 |
Child |
指向第一个子节点的指针 |
Peer |
指向兄弟节点的指针 |
Object |
指向实际对象(如AML方法定义) |
StartTimeSlicePassive 里遍历 Device(P2P0) 的子节点 Device(S1F0) 时,走的就是 Child 和 Peer 指针串起来的链表。对于 P2P0 而言,如果它有多个子节点(比如 S1F0、S1F1、S2F0),这些子节点通过 Peer 形成单向链表。
在被动执行队列中,每个节点对应一个 ACPI_OPERAND_OBJECT 方法的执行上下文。StartTimeSlicePassive 会把 S1F0 的 _ADR 方法包装成一个工作包(Work Package),放进执行队列,然后进入时间片循环。这个机制设计得挺巧妙:它把命名空间的逻辑树和执行调度的时间序列解耦了。树的遍历顺序是逻辑上的,但真正执行 _ADR 的时机受到解释器调度策略的影响。
这也就是为什么有些DSDT里的 _ADR 从静态代码看没问题,运行起来却“晚了一点”的底层原因。
3. 看懂_ADR返回值,才算看懂节点到PCI设备之间的那道桥
3.1 _ADR编码规则不是拍脑袋定的
_ADR 对于PCI设备来说,返回值标准格式为:
- Bit 31-16:Device Number(设备号)
- Bit 15-0:Function Number(功能号)
举个例子,如果PCIe设备在总线上的位置是 Device 2, Function 0,那么 _ADR 应该返回 0x00020000。Function 3就返回 0x00000003(实际上低16位直接是Function Number)。
多Function设备也很典型:一个物理PCIe插槽有多个Function,每个Function对应一个ACPI Device 节点,每个节点有自己的 _ADR,Device Number相同,Function Number不同,比如:
code复制Device(S1F0) { Name(_ADR, 0x00010000) } // Device 1, Function 0
Device(S1F1) { Name(_ADR, 0x00010001) } // Device 1, Function 1
Device(S1F2) { Name(_ADR, 0x00010002) } // Device 1, Function 2
PCIe规范里还有个特殊规则:如果一个设备的Function 0不存在,但Function 1存在,那么在配置空间中该设备仍然会被视为存在(因为多功能设备要求Function 0作为“头”)。但ACPI的 _ADR 不会去处理这种硬件细节,它只是固件和OSPM之间的“地址约定”,具体PCI配置空间的探测是PCI枚举逻辑的事。两者必须严格吻合,OSPM才能建立ACPI设备到PCI设备的映射。
3.2 从P2P0到S1F0,总线和设备号怎么变
在PCIe拓扑中,P2P0 作为PCIe Root Port,它在主总线(Bus 0)上通常占据某个Device Number。比如我们板子上它在Bus 0的Device 4,那 P2P0 的 _ADR 就是 0x00040000。
P2P0 下游的PCIe总线(次级总线)由Root Port配置决定,可能是Bus 1、Bus 2等等。S1F0 在这条次级总线上占据一个Device Number,比如Device 1。所以它的 _ADR 应该是 0x00010000。
注意,在ACPI命名空间里,我们看不到总线号。ACPI通过“父总线上有PCIe Bridge节点,Bridge节点下有Endpoint节点”这种层级来表达总线关系。OSPM在枚举时,会记录从Root Complex到Endpoint的路径,用 _ADR 一层层把实际的PCI地址拼起来。
所以 StartTimeSlicePassive 在处理 _ADR 时,实际上是在帮助OSPM完成这个“多层地址映射”建立过程的一环。一次 _ADR 执行影响的是某一个节点的地址,但整棵设备树的 _ADR 集合最终决定了OSPM能否正确构建完整的PCIe拓扑。
3.3 一个需要警惕的编码错误:把Function Number和Device Number搞反
我在实际调试中见过不少DSDT把 _ADR 编码搞反。比如一个设备硬件上是Device 1 Function 0,但DSDT写的是:
code复制Name(_ADR, 0x00000001)
这在语义上完全走了样。0x00000001 表示Device 0 Function 1。因为PCIe配置空间里,如果Function 1存在但Function 0不存在,OSPM读取配置空间时可能依然能探测到设备ID,所以从PCIe枚举角度这个设备能被看到;但从ACPI匹配角度,OSPM拿着 0x00000001 去匹配PCIe枚举结果,死活找不到这个ACPI节点对应的PCI设备,于是设备等于“半残废”状态。
这也是为什么 _ADR 返回值你不仅要看数值本身,还要结合PCIe配置空间里的 BDF(Bus/Device/Function) 现场对比。 如果是在运行中的系统上排查,直接读 /sys/bus/pci/devices/ 下的 * 目录名就能拿到实际BDF;对比DSDT里的 _ADR,问题经常一眼就能看出来。
4. StartTimeSlicePassive内部执行链路:节点怎么排队、_ADR怎么被调用
4.1 它到底是个什么级别的函数
从函数名拆解一下:
- Start:启动,说明这是时间片调度的入口。
- TimeSlice:时间片,解释器给每个AML方法分配的执行时间配额。
- Passive:被动,如果ACPI事件产生时OS处于被动上下文(比如内核线程,而不是中断上下文),所有要处理的AML方法都会被放到队列里延迟执行。
在ACPICA里,这个机制对应的是 AcpiEvQueueNotifyRequest、AcpiEvExecuteMethod 这类事件/方法入队接口,以及 AcpiEvRunCallbacks 这类执行队列处理函数。StartTimeSlicePassive 这种命名更偏向某个具体实现或移植层的封装,但设计思路是通用的。
它的工作概括起来就是:从全局执行队列里取出当前要处理的节点,判断节点类型,如果是PCI设备节点且有 _ADR,就准备执行该方法。 执行过程中会限制耗时,防止某个AML方法(比如有循环的变态方法)无限期占用解释器。
4.2 处理Device(P2P0)子节点Device(S1F0)的完整调用路径
我把实际调用链捋出来,方便你对照调试:
code复制ACPI事件触发(如_GLB、电源按钮、设备热插拔)
→ OSPM调用 AcpiEvaluateObject 或 AcpiWalkNamespace
→ AML解释器解析到 Device(P2P0) 下的 Device(S1F0)
→ 发现S1F0定义了_Method(_ADR)
→ 创建 _ADR 执行对象(ACPI_OPERAND_OBJECT)
→ 将执行对象加入全局队列
→ 调用 StartTimeSlicePassive
→ 判断当前时间片是否可用
→ 从队列头部取出 S1F0 的 _ADR 执行对象
→ 初始化参数(空参数,_ADR无入参)
→ 调用解释器执行AML字节码
→ 解释 Method 中的 Return(0x00010000)
→ 返回值存入执行对象
→ 将结果返回给调用者(OSPM的命名空间遍历逻辑)
这里有个细节:_ADR 通常是一个Method(方法)或者直接是一个 Name(名称对象)。如果是 Name(_ADR, 0x00010000),解释器根本不需要执行AML字节码,直接从命名空间对象里读值就行;如果是 Method(_ADR) { Return(0x00010000) },就必须走完整的AML解释执行路径。这两种写法在处理性能上有差异,尤其在大量设备节点逐一执行 _ADR 时,使用 Name 的效率更高,因为不需要解释器进入方法执行上下文。
4.3 “被动”意味着什么,为什么它在启动阶段特别重要
在ACPI架构里,AML方法可以选择在哪种上下文执行。StartTimeSlicePassive 里的 Passive 强调它运行在被动上下文,不是ACPI中断/事件上下文(Active Level)。
区分这个很重要:
- Active Level(主动级):通常运行在中断上下文,响应ACPI事件时直接执行AML。优点快,缺点是不能做复杂操作,容易阻塞中断处理。
- Passive Level(被级别):将AML执行排队到内核工作线程/任务队列,等中断返回后再逐个执行。不干扰中断路径,适合做耗时操作。
启动阶段ACPI枚举是典型的被动级执行。当OSPM枚举PCIe设备时,会走到 P2P0 的子节点 S1F0,触发 _ADR 方法,这个方法在ARM/x86内核的ACPI子系统里是在 acpi_ns_evaluate 时被调用的,而这个函数又可能处于某个ACPI通知回调的工作队列里。StartTimeSlicePassive 就是在工作队列上下文里维护执行时间片、“放行” _ADR 的开关。
如果这个开关出问题——比如时间片分配太短,_ADR 方法还没执行完就被切出去了——整个设备枚举就会非常不稳定。这就是为什么我那次调试要一路追到这个函数的原因。
4.4 多节点顺序执行时容易忽略的依赖
StartTimeSlicePassive 内部对多个子节点的遍历不是简单的for循环。它要把“当前节点执行完后的返回值”和“下一个节点是否允许执行”挂钩。
举个例子,如果 P2P0 的 _STA 返回0(设备不存在或隐藏),那么OSPM通常会跳过它的所有子节点,包括 S1F0。这时 S1F0 的 _ADR 根本不会被调用。解释器在 StartTimeSlicePassive 里会检查父链上每个节点的状态对象,这个检查逻辑通常在 AcpiNsGetDeviceCallback 或 AcpiNsWalkNamespace 的回调里完成。
反过来,如果父节点 _STA 正常,但 _ADR 还没读取(因为父节点的Adress未解析),子节点的 _ADR 也不该先跑。先进先出、父先子后是ACPI枚举的基本原则。这套顺序依赖于命名空间遍历的递归深度优先策略,而 StartTimeSlicePassive 的时间片调度必须遵循这个策略,不能在处理节点时跳来跳去。
5. 真刀真枪调试:从_start到_OSC,ACPI枚举那段“卡住”的排查实录
5.1 问题现象和现场日志定位
回到我开头说的现象:启动日志里,PCI设备枚举完成,ACPI设备树遍历完成了,但操作系统里 /sys/bus/acpi/devices/ 下面就是找不到那个本该是 PNP0C02 的 S1F0 节点。
先看dmesg关键日志:
code复制ACPI: Added Device \_SB_.PCI0.P2P0
ACPI: Added Device \_SB_.PCI0.P2P0.S1F0
设备明明Added了,为什么最终没有关联PCI设备?再看PCI枚举日志:
code复制pci 0000:01:00.0: [144d:a808] type 00 class 0x010802
pci 0000:01:00.0: [Firmware Bug]: Device not claimed by any ACPI device
“Firmware Bug”这个词一出现,基本可以断定是ACPI节点和PCI设备BDF匹配失败了。
于是我做了两件事:
- 读
/sys/bus/pci/devices/0000:01:00.0/下的路径,确认实际BDF是01:00.0(Bus 1, Device 0, Function 0)。 - 反编译DSDT,查看
Device(S1F0)的_ADR值。
结果DSDT里写的是:
code复制Name(_ADR, 0x00010000) // Device 1, Function 0
而实际硬件是 Device 0, Function 0。一个在总线1上,一个在总线0?这不是简单的Device Number差1,而是ACPI固件开发者把设备号定义错了。应该返回 0x00000000(Device 0, Function 0),写成了设备1。
5.2 为什么StartTimeSlicePassive执行链路里的断点能“现场抓现行”
发现问题后,还想进一步确认,就是S1F0的 _ADR 方法到底有没有被解释器执行,返回值是什么。当时我用的工具是ACPICA的 acpiexec 和内核的 CONFIG_ACPI_DEBUG 日志。
在 StartTimeSlicePassive 这类函数里,断点怎么打?不能直接改内核源码去看(当然你也可以临时加printk),更推荐的做法是:
- 用
acpiexec -b加载DSDT,做静态解析,用find \_SB_.PCI0.P2P0.S1F0检查节点是否存在,用evaluate \_SB_.PCI0.P2P0.S1F0._ADR手动执行一遍_ADR,直接看解释器返回什么。 - 如果问题只在运行态出现,就在内核里开
CONFIG_ACPI_DEBUG,在drivers/acpi/acpica/下打开组件调试,设置:
bash复制echo 0xFF > /sys/module/acpi/parameters/debug_layer
echo 0x02010520 > /sys/module/acpi/parameters/debug_level
把 ACPI_EXECUTER、ACPI_NAMESPACE 和 ACPI_EVALUATOR 相关的调试信息打开。这时dmesg会打出 StartTimeSlicePassive 执行时经过的节点路径和 _ADR 的求值日志,类似:
code复制ACPI: Executing Method \_SB_.PCI0.P2P0.S1F0._ADR
ACPI: Completed Method \_SB_.PCI0.P2P0.S1F0._ADR, Return Value = 0x10000
看到返回值是 0x10000,同时比对PCI枚举结果 0000:01:00.0,就能把锅钉在固件上。
5.3 复现和验证:改DSDT后如何验证修复
我的修复方案很直白:让固件工程师把 S1F0 的 _ADR 从 0x00010000 改成 0x00000000。但在改固件之前,为了快速验证,可以用一个折中手段——在Linux内核里用 acpi_rsdp 或者 initrd 里覆盖DSDT。
具体做法:
- 解包DSDT:
bash复制sudo cp /sys/firmware/acpi/tables/DSDT /tmp/dsdt.aml
iasl -d /tmp/dsdt.aml
-
编辑
dsdt.dsl,把Device(S1F0)里的_ADR改成0x00000000。 -
重新编译覆盖:
bash复制iasl -tc /tmp/dsdt.dsl
mkdir -p /boot/acpi_override
cp /tmp/dsdt.aml /boot/acpi_override/
- 把
acpi_override目录加入引导镜像,或者用acpi_overrideinitrd 方式加载。
重建initrd后重启,dmesg里的“Firmware Bug”消失:
code复制pci 0000:01:00.0: ACPI: \_SB_.PCI0.P2P0.S1F0 [PNP0C02]
这一步验证了根因,固件修完后,设备节点和PCI设备的关联完全正常,NVMe设备也能在ACPI节点下查到对应的 _ADR、_PRW 等控制逻辑。
5.4 这个案例教会我的:ACPI地址匹配,永远要双向核对
设备枚举失败、ACPI关联失败,第一反应不要只盯着DSDT,也不要只盯着PCI配置空间,必须把PCI枚举结果和ACPI命名空间结果放在一起对比。_ADR 匹配的桥就是中间那层 StartTimeSlicePassive 之类执行入口,但匹配判断本身发生在OSPM的上层逻辑,比如 acpi_pci_bind 或者 acpi_bind_one。
我建议每个做BSP/固件调试的人都建立一个自己的对照表:
| 项目 | PCIe枚举结果 | ACPI命名空间节点 | ACPI _ADR | 是否匹配 |
|---|---|---|---|---|
| Root Port | Bus 0, Dev 4, Func 0 | _SB.PCI0.P2P0 | 0x00040000 | 匹配 |
| NVMe Slot | Bus 1, Dev 0, Func 0 | _SB.PCI0.P2P0.S1F0 | 0x00010000(错误) | 不匹配 |
这个表建好,问题定位就成功了一半。
6. 时间片里的暗坑:ADRLearning_ADR_时候最容易踩的三个雷
6.1 雷区一:Device Number的0基还是1基
很多开发者的惯性思维是“第一个设备就是1”,于是写 _ADR 时设备号从1开始。但PCI Device Number 是从0开始的,和Linux的 /sys/bus/pci/devices/ 里看到的名字完全一致。比如实际是 0000:01:00.0,Device Number就是0,所以 _ADR 低位必须是 0x00000000。
我在调试过的多个平台里发现,这个问题出错的频率极高,尤其是那些从旧平台复制DSDT的工程师,经常忘了改设备号。所以每次看到“PCI设备claimed by no ACPI device”,第一件事就是核对硬件设计图上的Device Number,而不是直接在DSDT里猜。
6.2 雷区二:Bridge节点本身要不要有_ADR
P2P0 作为Root Port,它本身是在主总线上可枚举的PCI设备,所以必须有 _ADR。但有些DSDT在Root Port节点上不加 _ADR,而是用 _HID 加 _CID 来让OS识别成平台设备。这在特定场景下也能工作,但会导致OSPM无法把ACPI设备树和PCI拓扑一一对应,直接影响下游设备的电源管理和热插拔逻辑。
如果你的平台有需要热插拔的PCIe Slot,强烈建议所有Bridge和Endpoint都提供 _ADR,而不是依赖 _HID 从平台设备路径绕。
6.3 雷区三:在被动执行时修改命名空间或执行方法
StartTimeSlicePassive 在被级上下文执行时,尽量避免在AML方法里做 LoadTable、External 这种动态修改命名空间的操作。原因很简单:被动调度的时间片是分给当前节点的,如果你在 _ADR 执行过程中动态加载新的表,命名空间树被修改,正在遍历的 Peer 指针可能失效,导致解释器访问到已释放的节点。
实测中,我在某个平台遇到过 S1F0 的 _ADR 执行到一半,返回结果直接变成垃圾值。后来发现是这个方法内部调用了 LoadTable,动态加载了一张SSDT,触发了命名空间树重建。解决办法是把 LoadTable 移到 _INI 或者其他初始化阶段,不要在 _ADR 里做。
6.4 雷区四:被动调度下方法超时不返回
AML方法本身的执行时间理论上不可控,但在被动时间片机制里,如果方法内部存在过深的递归或者死循环,解释器可能无法在当前时间片内完成执行。StartTimeSlicePassive 的实现在不同厂家略有差别,有的会强制中断,有的会退出循环,有的干脆不响应。结果是 _ADR 的执行回调永远不返回,OSPM的枚举线程卡死。
这个坑我在某板卡上遇到过,当时的表现是设备枚举卡在 S1F0 附近,系统启动超时。从ACPI源码看 _ADR 就是一个简单 Return(0x00010000),但解释器就是过不去。后来发现 S1F0 的依赖表里有循环引用,导致方法执行上下文反复创建。这种问题定位比较费劲,建议用 acpiexec 加载同一份DSDT做对照,如果在 acpiexec 里也卡住,就是AML字节码解释执行路径的问题,跟OS调度和PCI枚举完全无关。
7. 写在最后:一套可以直接用在我后续项目里的排查方法
这次把 StartTimeSlicePassive、Device(P2P0)、Device(S1F0)、_ADR 四者串起来查了一遍之后,我的收获是:ACPI解析的底层细节虽然复杂,但有清晰的套路可循。如果你在项目里也遇到类似的ACPI枚举或设备关联问题,我的建议是:
第一,先把Linux里 CONFIG_ACPI_DEBUG 的日志配起来,这是最快看到 _ADR 执行路径的方法。第二,准备一份反编译好的DSDT,用 acpiexec 静态测试关键节点的 _ADR、_STA、_PRW 方法,把纯解释器层面的问题和运行态调度的问题分开。第三,对比PCI枚举结果时,务必把Bus/Device/Function都写完整,不要只看一个维度。
最后,不要忽视“时序”这个因素。ACPI设备树和PCI设备的匹配,不只是数值对得上就行,还要求执行时机合适。StartTimeSlicePassive 这类调度函数的存在,本身就说明了AML方法执行在ACPI子系统中是考虑性能与安全性的,我们把DSDT里的方法定义好还不够,还得让它按正确的顺序、正确的时间跑完。这个思路用到以后做热插拔和电源管理调试上,能省掉大量无用功。
