从 PCI 配置读取 USB 设备供应商 ID 和设备 ID space (EFI)
Reading USB device Vendor ID and Device ID from PCI config space (EFI)
我想通过 EFI 程序获取插入的 USB 设备的供应商 ID 和设备 ID。我可以读取整个 PCI 配置 space 我找到了我的 USB 设备插入的 USB 主机控制器 我也可以读取为该控制器寻址的整个内存,但我不知道我在这个内存中搜索的到底是什么获取这些 ID。有人可以帮助我吗?
PCI 配置space 显示 PCI 和 PCI Express 设备,而不是 USB 设备。
PCI 配置space 将显示 USB 控制器的供应商和设备 ID,但不会显示连接的设备。为此,您必须通过 reading/writing USB 寄存器枚举 USB 总线。
请注意,接管 USB 控制器会破坏 currently-running USB 堆栈并终止您的 USB 键盘和启动设备。
如果你在 UEFI shell,也许你可以在 devtree 的输出中找到你需要的东西。
如果您正在编写自己的 UEFI DXE 代码,则必须查询 USB 驱动程序。
USB 协议定义了一个设置数据包,xHCI 驱动程序必须在 xHCI 中构建该数据包,xHCI 硬件将转换为 USB – 没有像 PCIe 那样直接访问此信息的寄存器。首先,我将在 windows 上列出整个 USB 设备枚举过程,如果使用 xHCI 或 eHCI,它会有所不同。
xHCI
硬件重置后,所有根集线器端口都将处于断开状态。端口将通电并等待设备连接。当硬件检测到设备连接时,它会将 PORTSC 寄存器中的当前连接状态和连接状态更改标志设置为 1,此操作将导致 PSCEG 信号同时变高,因为它是 PORTSC 寄存器的逻辑或位。该信号在控制器中生成端口状态更改事件,这会导致 xHCI 控制器硬件将数据包(称为传输请求块)放在事件环上。事件环段和 table 从 non-paged 池中分配并在 xHCI 控制器枚举期间初始化,可能在 xHCI 驱动程序的 StartDevice
例程期间初始化,该例程在加载时调用;它还会初始化设备 MMIO 中的事件环寄存器 space。
在环上排队事件会导致硬件在 MSI-X table 中的特定偏移处触发中断(BAR 的 MSI-X 偏移量和 BAR 编号是存储在 xHCI 控制器的 PCIe 配置 space 中的 MSI-X 功能中;因此,MSI-X table 在 MMIO space 中)。主事件环总是接收所有端口状态变化事件。主要事件环总是映射到第一个 MSI-X 中断。 MSI-X 中断将作为标准 PCIe MSI 传送到 LAPIC,它将在 IRR 中为 MSI-X table 存储数据中指定的向量排队中断。 xHCI 驱动程序先前 条目对应于使用内核和 HAL API 的向量。 xHCI 驱动程序的 ISR 实现 TRB 数据包是端口状态更改事件;它可以评估端口 ID 以确定作为更改事件源的根集线器端口(例如 5),然后检查第 5 个 PORTSC 寄存器以查看发生了什么更改,它在与操作基础的特定偏移处访问,这是在引导时 PCIe 枚举期间分配的 xHCI 控制器的 PCIe 配置 space BAR 中地址的偏移量(MMIO 基数);公式为 Operational base + (400h + (10h*(n-1)))
,其中 n 是通过 MaxPorts 的端口号 1,Operational base 是 CAPLENGTH 寄存器中的值 + MMIO base。
xHCI 驱动程序通知 Root Hub 驱动程序设备已到达(我想通过调用 Root Hub callback function,它可能通过 Root Hub 的 PDO 访问),然后 Root Hub 驱动程序创建一个PDO 并通知 PnP 管理器 child 设备集已针对 Root Hub 更改。 xHCI 驱动程序自动分配插槽 ID 并在调用回调函数之前静默执行地址设备 TRB 过程并当场提供插槽 ID,或者集线器驱动程序必须通过向 xHCI 控制器发送 URB 来启动此过程以请求当通知某个端口 ID 上有端口状态更改时,分配插槽 ID 并返回给它(我不确定。而且我不确定哪些数据结构由集线器驱动程序控制,而不是 xHCI驱动程序,所以这是一个猜测)。 xHCI Driver收到URB后,会在command ring上发送enable slot TRB,并从event ring上的command completion TRB中获取slot ID;它将在 xHCI 控制器在 non-paged 池中维护的设备上下文基数组中分配一个设备上下文结构(称为输出设备上下文)和指向它的指针。在 URB 响应中,集线器驱动程序收到插槽 ID;集线器驱动程序然后在内存中分配一个输入设备上下文。 Input Device Context 中的 Input Control Context 结构中的 slot context 和 endpoint 0 context 的 Add Context 标志设置为 1 以指示它们需要添加。输入设备上下文结构中的插槽上下文然后分配有端口号、根字符串和端点数。输入上下文中的端点 0(又名默认控制)上下文数据结构必须配置为 TR 出队指针(指向它分配的传输环)、EP 类型、错误计数和最大数据包大小字段的有效值。 MaxPStreams、Max Burst Size 和 EP 状态值应设置为 0。Input Context 结构指针由集线器在地址设备命令中发送,该地址设备命令寻址到新设备的 Slot ID,通过 URB 发送到 xHCI驱动程序并将地址设备 TRB 放置在命令环上,主机控制器会将输入上下文复制到插槽的 DCBA 条目指向的输出上下文。
然后集线器驱动程序将 URB 发送到 xHCI 驱动程序以获取 fol 中的设备描述符欠款形式:
Status = SubmitRequestToRootHub(RootHubDeviceObject,
IOCTL_INTERNAL_USB_SUBMIT_URB,
Urb,
NULL);
槽ID返回到集线器后发送的所有URB都将包含槽ID。它还会 link ChildDeviceObject->DeviceExtension->UsbDeviceHandle
到 Urb->UrbHeader.UsbdDeviceHandle
,这使得 PDO 成为分配给可通过 URB 访问的新设备的集线器。 RootHubDeviceObject
是 Hub Driver 的 PDO,由 xHCI Controller Driver(或 usbxhci-usbport 对)拥有,将在本例程中调用 IoCallDriver
时使用。 URB 的类型为 GET_DESCRIPTOR
。然后用 IRP_MJ_INTERNAL_DEVICE_CONTROL
的主代码初始化 IRP,并用 StackPtr->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
作为参数之一的 URB 初始化堆栈位置。然后它在 xHCI 驱动程序拥有的 RootHubDeviceObject
上调用 IoCallDriver
。
xHCI 驱动程序将使用 URB 中指定的插槽 ID 来索引 DCBA 数组和门铃数组。它转到控制(默认,0)端点描述符,它位于 DCBA[slotID] 指向的插槽设备上下文数组中的索引 1(插槽上下文位于索引 0),并且它将写入一个设置阶段 TD(其中包含单个设置 TRB)到默认(控制)端点描述符中指定的入队指针(我假设当 xHCI 控制器处理地址设备命令时,它自动设置为与最初在输入设备上下文中的出队指针相同的地址),这是 xHCI 控制器使用 PCIe TLP 事务读取的 RAM 中的物理地址。在TRB中,指定TRB类型(Setup);传输类型(IN);传输长度(设备描述符大小 = 18);即时数据 = 0(不确定这是什么,但似乎只有设置阶段将其切换为 1);完成时中断(否); bmRequestType = 80h和bRequest = 6一起指定GET_DESCRIPTOR请求类型; wValue设置为type: Device Descriptor,即0100h;然后 wLength 为 18(设备描述符的长度)。然后它推进 Endpoint 0 Transfer ring Enqueue Pointer(将前一个 TD 的大小添加到它)。然后它在它写入的新入队指针的位置写入一个数据阶段 TD;但是,实际上,它使用了在 xHCI 枚举上通过 MMIO space 定义的 xHCI 驱动程序的虚拟地址(它在启动设备例程中的 Parameters.StartDevice.AllocatedResourcesTranslated 中的 CM_RESOURCE_LIST 上使用了 MmMapIoSpace
) 写入 RAM 中的位置,因为系统软件不能像 PCIe 设备(主机控制器)那样使用物理地址。 Data Stage TD由一个TRB组成,TRB类型设置为Data Stage TRB;方向 = 1; TRB传输长度=18;链位=0; IOC = 0(不中断,因为只有状态阶段会导致中断,即完成时);即时数据 = 0;数据缓冲区指针(xHCI 控制器将写入响应的 64 位物理地址,从集线器驱动程序提供的虚拟地址转换而来);和循环位(当前生产者循环状态(根据环绕环的排队指针切换到 1 或 0。如果生产者遇到 link TRB(它在写入之前读取),则生产者将循环位从 0 切换到 1到一个位置以确保没有 Link TRB 已经指向环的开始))。然后它再次推进 Enqueue 指针。最后它写入一个由单个状态组成的 Status Stage TD TRB with TRB type = Status Stage TRB; Direction = '0'; TRB transfer length = 0; Chain bit = 0; Interrupt On Completion = 1; Immediate data = 0; Data buffer pointer = 0(没有一个因为它是只是一个状态阶段);和循环位 =(当前生产者循环状态)。
然后 xHCI 驱动程序使用插槽 ID 索引到门铃阵列,并将序列写入该索引处的门铃寄存器,这表明控制 EP 0 排队指针已更新。然后主机控制器开始行动并读取 TRB,增加出队指针;并且,当它等于入队指针时,它停止。对于每个 TRB,它 sends the appropriate packet to the device。当它处理 Status 阶段 TRB 时,它会在 Event Ring(我认为是 ring 0)上引起中断,这会导致 MSI-x 中断,如前所述,到指定的 CPU 的 LAPIC向量,它将被 xHCI 控制器 ISR 和 DPC 拾取。 ISR 将部署完成 IRP 的 DPC。描述符将位于集线器驱动程序在 URB IRP 中指定的虚拟地址。
集线器驱动程序将它在 URB IRP 中收到的信息插入到 PDO->DeviceExtension
字段中,该字段是一个指向驱动程序定义结构的指针,它可以用它来做它想做的事情,这意味着信息本质上是已缓存,不再需要将 URB 发送到 xHCI 驱动程序。对于设备在设备描述符中报告的每个配置编号,集线器还向 xHCI 驱动程序发送 GET_CONFIGURATION 个 URB。然后 xHCI 驱动程序将该配置值传递给具有正确配置编号的 GET_CONFIGURATION TRB 中的设备,并且该配置编号的整个配置层次结构将返回到 URB 中指定地址的集线器驱动程序。然后它使用 Type 参数调用 IoInvalidateDeviceRelations()
BusRelations
的 er 和指向其由 xHCI 驱动程序分配的 PDO(物理设备 object)的指针。 PnP 管理器使用 IRP_MN_QUERY_DEVICE_RELATIONS
请求查询 PDO 的设备堆栈以获取总线上的当前设备列表;为此,它初始化一个 IRP 结构(理想情况下,根据设备 object 中的 stacksize
提示重用后备列表中的一个;否则,它直接从 non-paged 池中为一个新的)。 IRP 通过 CurrentStackLocation
成员指向堆栈(与 IRP 相邻)。然后它为它想要执行的调用初始化第一个堆栈位置(在这种情况下,IRP_MJ_PNP
的主要功能和 IRP_MN_QUERY_DEVICE_RELATIONS
的次要功能)。然后它调用发送的 PDO 的设备堆栈顶部的驱动程序,它可能是一个上层过滤器驱动程序(它不会实现那个次要功能,函数体将是向下传递它的代码——我们假设暂时没有)。因此,堆栈的顶部将是集线器的 FDO(它使用 IoGetAttachedDevice 到达,这是堆栈的顶部)。它使用 IoCallDriver(*FDO, *IRP)
调用它,它是 IofCallDriver
的包装器,它通过递减 CurrentStackLocation
指针来获取下一个堆栈位置,这导致它通过 C 的规则指向下一个堆栈位置指针算法(这是第一个堆栈位置,因为指针在它之后被初始化),然后它使用堆栈位置中指示的主函数号 IRP_MJ_PNP
索引到驱动程序的 MajorFunction
数组FDO 的 object 已传递给 IoCallDriver
(集线器驱动程序)并调用数组中该位置的函数。
该调用的代码如下所示:
return FDO->DriverObject->MajorFunction[StackPtr->MajorFunction](FDO,
Irp);
您可以看到它通过了 IRP。这允许 IRP_MJ_PNP
的 USB 集线器驱动程序的函数处理程序检查当前堆栈位置的次要函数,然后调用正确的内部函数。对于每个 child 设备,处理程序引用 DEVICE_RELATIONS 结构中的 PDO,它只是一个 PDO 指针数组。然后它将 Irp->IoStatus.Information
设置为指向数组的指针和 returns。即插即用管理器然后查看 PDO 数组并将地址与它已经枚举的设备树上的 PDO 地址进行比较。如果有新地址,它会查询设备和实例 ID 以及资源要求;并且,如果任何 PDO 已被标记为非活动,它还会使用与前面描述的相同的 IRP 初始化过程向这些 PDO 发送 IRP_MN_SURPRISE_REMOVAL
(设备的 FDO 将不会实现意外删除功能并将其传递下去到集线器驱动程序)并且集线器驱动程序将禁用设备并释放分配给它的硬件资源。
为了查询设备 ID 和实例 ID,PnP 管理器向数组中的每个 PDO 发送一个 IRP_MN_QUERY_ID
(一个用于设备 ID,另一个用于实例 ID),其指针是新的(这将是根集线器驱动程序创建的新设备的 PDO)。对于请求设备 ID 的 IRP(设备 ID 是 Windows' bus-specific 设备 ID + 供应商 ID + 子系统 ID + 子系统供应商 ID 修订的串联,它从设备和总线前缀又名. 枚举器,例如 USB),它发送一个 IRP_MN_QUERY_ID
,但将堆栈位置的 Parameters.QueryId.IdType
成员初始化为 BusQueryDeviceID
。为了响应设备 ID 查询,集线器驱动程序需要使用其总线前缀向设备查询构建和连接设备 ID 所需的信息,但它在 PDO 创建后就已经这样做了,因此它可以只使用DeviceExtension
信息被插入其中。实例 ID 是一个设备标识字符串,用于将设备与其他相同类型的设备区分开来,它可能使用 USB 描述符中的 iSerialNumber
值或一个简单的增量——它是特定于总线的。它们一起形成一个 DIID(设备实例 ID)。在 IRP 中使用 Parameters.QueryId.IdType = BusQueryInstanceID
后,PnP 管理器在单独的调用中查询 InstanceID。
集线器驱动程序通过安装在 DriverEntry 上的 IRP_MJ_PNP 处理程序接收 PnP 管理器查询的 PDO,现在使用设备描述符中的字段构建 DIID,该字段先前已解析并插入 into the DeviceExtension
of the PDO 其中 usDeviceId
似乎是已经串联的前缀 + ProductID + VendorID。请记住,DIID 是 Bus + Device ID + Instance ID,而 Device ID 是 Bus + Vendor + Product ID。然而,它对这些 ID 执行一些固定操作,将它们全部更改为 usbstor .inf 文件识别的 usbhub 标准格式。 DIID 最终将如下所示:USB\VID_<num>&PID_<num>\<InstanceID>
。只有 PDO 有 DIID 或 Hardware/Compatible ID。
PnP 管理器然后使用返回的 DIID 索引到位于 HKLM\SYSTEM\CurrentControlSet\Enum\Bus\DeviceID\InstanceID
的注册表中。
其中的 classguid 值导致 HKLM\SYSTEM\CurrentControlSet\Control\Class\<GUID>
下的 class 子项,例如,它可能是键盘 class。这些值由驱动程序.INF 文件填写。
The PnP manager checks the registry for the presence of a corresponding function driver, and when it doesn’t find one, it informs the user-mode PnP manager of the new device by its DIID. The user-mode PnP Manager first tries to perform an automatic install without user intervention. If the installation process involves the posting of dialog boxes that require user interaction and the currently logged-on user has administrator privileges, the user-mode PnP manager launches the Rundll32.exe application (the same application that hosts Control Panel utilities) to execute the Hardware Installation Wizard (%SystemRoot%\System32\Newdev.dll). If the currently logged-on user doesn’t have administrator privileges (or if no user is logged on) and the installation of the device requires user interaction, the user-mode PnP manager defers the installation until a privileged user logs on. The Hardware Installation Wizard uses Setupapi.dll and CfgMgr32.dll (configuration manager) API functions to locate INF files that correspond to drivers that are compatible with the detected device.
它通过在 .INF 文件中给它们一个 ranking by looking for Compatible IDs 来选择最相似的 .INF 文件希望与从 DIID 生成的硬件/兼容 ID 相匹配,这些 ID 被插入到设备扩展中。如果它找到一个,那么它会安装驱动程序。插入的每台新设备都会进行安装,本质上只是使用该 DIID 下的正确信息填写注册表。
Installation is performed in two steps. In the first, the third-party driver developer imports the driver package into the driver store, and in the second step, the system performs the actual installation, which is always done through the %SystemRoot%\System32\Drvinst.exe process.
控制权传回 PnP 管理器,它使用注册表项按以下顺序加载驱动程序:
- 在设备枚举键的 LowerFilters 值中指定的任何 lower-level 过滤器驱动程序。
- 在设备的 class 键的 LowerFilters 值中指定的任何 lower-level 过滤器驱动程序。
- 由设备枚举键中的服务值指定的功能驱动程序。此值被解释为 HKLM\SYSTEM\CurrentControlSet\Services.
下的驱动程序密钥
- 在设备枚举键的 UpperFilters 值中指定的任何 upper-level 过滤器驱动程序。
- 在设备 class 键的 UpperFilters 值中指定的任何 upper-level 过滤器驱动程序。
USB 驱动程序将有一个中间 devnode – 如果它是复合设备则为 usbccgp,如果它是大容量存储设备则为 usbstor,可以看到 here。当集线器驱动程序发送 DIID 时,即插即用管理器加载的是 usbstor,如上图所示。 (我们需要中间 USB 存储节点将通用 disk.sys IRP 转换为 URB 并处理特定于 USB 的驱动器配置,而不是将所有功能塞入 usbhub.sys)。
它加载驱动程序并调用每个驱动程序的 DriverEntry
函数,然后调用 AddDevice
例程,如果它们尚未 运行 (对于使用相同驱动程序的另一个 USB 设备);否则,它只调用 AddDevice
例程。 AddDevice
例程为传递的 PDO 创建一个 FDO。在其 AddDevice
例程中,过滤器驱动程序创建一个过滤器设备 Object (FiDO) 并将其附加到设备堆栈 (IoAttachDeviceToDeviceStack)。然后,PnP 管理器创建一个设备节点并将其与 PDO 相关联。 PnP 管理器在为设备 ID 发送 IRP 的同时,已经使用 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
更早地获取了总线设备(集线器驱动程序)对设备资源的意见。 PnP 管理器现在使用 IoCallDriver
在具有指定 FDO 的新设备节点顶部发送 IRP_MN_FILTER_RESOURCE_REQUIREMENTS
。只有 FDO 驱动程序处理这个,它会改变集线器驱动程序无法预测的设备 object 的任何要求。 USB 大容量存储设备不需要中断,因为它只使用主事件环。如果是,它会在对 IRP 的响应中指定 number of MSI-x messages it requires),一旦 PnP 管理器将资源分配给设备,它就会向设备堆栈发送 IRP_MN_START_DEVICE
IRP。尽管每个 USB 设备都可以有一个单独的中断和相应的事件环,但这并不重要,因为它始终是响应中断的同一最低级别驱动程序:xHCI 驱动程序; USB 设备没有要注册的 ISR。因此所有USB设备都可以共享单事件环和单中断。
在处理启动设备 IRP 的 usbstor 例程中,IRP 在例程设置 IoCompletionRoutine
后向下传递到总线设备 (usbhub)。 IoCompletionRoutine
,当它最终被调用时,将发送一个 GET_CONFIGURATION URB,该 URB 将向下传递到它的 PDO(由 usbhub 拥有)并且 usbhub 将呈现它之前缓存在该 PDO 设备扩展中的配置。 Usbstor 最终决定要使用的配置并发送 SET_CONFIGURATION URB。 Usbhub 将使用端点(即 ISOCH IN、INTERRUPT IN)填充来自缓存配置的输入上下文,并将输入上下文指针添加到 URB。 Usbhub 然后添加更多信息,如插槽 ID,并将 URB 发送到 xHCI 控制器,它拾取它并在默认端点 0 传输环上插入一个 TRB,用于主机控制器将填充插槽的输出设备上下文的设备根据输入控制上下文输入设备上下文,并通知设备有关所选配置的信息。
Usbstor 将分配一个 PDO 并调用 IoInvalidateBusRelations
。当它到达它时,usbstor 将请求存储其 PDO 的设备信息,该信息早先属于 usbhub,它将 DIID 转换为 usbstor 可识别的标准格式 disk.sys .inf 驱动程序(格式 \Disk&Ven_xxx&Prod_xxx&Rev_x.xx) 并附加 USBSTOR\ 前缀,这将允许注册表加载 disk.sys 和 partmgr.sys 作为过滤器存储在 disk.sys class 子项。 Usbstor 现在是设备的总线设备。
Disk.sys FDO 将检查维护的驱动程序 table 以查看系统中枚举了多少磁盘 (N) 并将 FDO 命名为 \Device\HarddiskN\DRN
。 Partmgr.sys 创建一个符号 link \Device\HarddiskN\Partiton0
到 \Device\HarddiskN\DRN
。然后 Partmgr 将调用 IoReadPartitionTableEx
并为每个分区创建分区 PDO,将它们命名为 \Device\HarddiskN\PartitonM
等等。对于每个分区,它向 volmg 发送一个 IOCTL_INTERNAL_VOLMGR_PARTITION_ARRIVED
IRP并向分区提供磁盘签名和偏移量。 Volmgr 为每个卷创建一个卷 PDO,并在 \Device\HarddiskN\PartitonM
和它分配给卷 PDO 的卷 PDO 名称 \Device\HarddiskvolumeX
X >= 1
之间创建 symlinks(实际上是因为这个符号link,永远无法访问具有该名称的分区 PDO,并且 PDO 本身缺少 devnode 并由 partmgr 内部管理)并向 MntMgr 发送一个 IRP_MJ_DEVICE_CONTROL
请求,通过调用指定 IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION
每个卷 PDO 的 IoBuildDeviceIoControlRequest(Harddiskvolume1 始终是 disk.sys 的 \Device\Harddisk0\DR0 上的第一个卷)。安装管理器通过查询 volmgr 以获取位于系统 object 树的设备目录(例如:“\Device\HarddiskVolume1")、为卷生成的唯一 ID 和建议的永久符号 link 名称,例如\DosDevices\D:
.
永久驱动器号和挂载点以相同的数据字段存储。值的数据称为唯一 ID,由 volmgr 以 IOCTL_MOUNTDEV_QUERY_UNIQUE_ID
提供给 mntmgr。基本磁盘的唯一 ID 是分区的签名和偏移量。如果安装管理器的数据库尚未包含与唯一 ID 配对的卷的永久驱动器号名称,则安装管理器使用建议的名称。否则,它会忽略该建议并使用其永久名称数据库中的驱动器盘符名称。它将 \DosDevices\D:
绑定到唯一 ID 并开发一个 GUID,并将 \??\Volume{GUID}
形式的 GUID 绑定到其名称中的唯一 ID space,然后创建 object 经理名称space 符号 link 之间。稍后的挂载点,即 \DosDevices\D:\mymount
也将与唯一 ID 配对。在上图中,MBR 驱动器包含 A:(系统保留)和 C: 分区,很明显它们具有相同的 MBR 签名和不同的偏移量。 D: 驱动器是 GPT 驱动器,具有 GPT 签名和偏移量。 E: 是具有 USBSTOR 特定签名和偏移量的 USB MBR 驱动器。
第一次打开C盘的文件时,不会挂载文件系统,所以在IopParseDevice
中解析文件路径字符串时调用IopCheckVpbMounted
卷(C: 对应于设备名称 \Device\HarddiskVolume2
,因为挂载管理器创建了符号 link),它将调用 IopMountDevice
,因为 VPB->DeviceObject == NULL
,它发送一个 IRP_MJ_FILE_SYSTEM_CONTROL/IRP_MN_MOUNT_VOLUME
到每个使用 IoRegisterFileSystem
注册自己的已注册文件系统注册文件系统。被调用的 FSs 处理 IRP_MN_MOUNT_VOLUME
并确定它们的文件系统是否在媒体上,如果是,文件系统创建文件系统卷设备 Object (VDO) 并将其放入 VPB。 C:
给出设备,\file
给出文件 object。 \
是根目录object。 IoGetRelatedDeviceObject
从文件 object 获取 VDO 堆栈的顶部(在 FileObject->Vpb->DeviceObject
上执行 IoGetAttachedDevice
)。
NTFS 驱动程序内部结构:
Windows USB 驱动设备树
xHCI控制器是如何枚举的
在系统启动时,pci.sys 将由 pci.sys 加载,它所做的其中一件事是调用 IoInvalidateDeviceRelations
,这会触发 PnP 管理器发送一个 IRP_MN_QUERY_DEVICE_RELATIONS
,它以 PDO 列表响应。它通过使用 MCFG
ACPI table 的 Base address of enhanced configuration mechanism
字段获取 PCIe 配置的基础 space (PCIEXBAR) 然后遍历它来当场创建列表在 4096 字节边界上,对于它在边界上找到的任何 Vendor/Device ID,它创建一个 PDO 并将其与配置编号配对。其中一个设备将是 xHCI 控制器。它经历了与上述完全相同的过程,最终创建 DIID 并检查注册表等,这将导致加载 xHCI 驱动程序; PnP 管理器还向总线询问其 child 需要的资源,并向 PDO 半堆栈提供 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
(这也将由 ACPI 总线过滤器处理)。 xHCI Driver加载完成后,会发送一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS
给xHCI Driver,这样它就可以对资源需求进行修改。 xHCI 控制器 PDO 的 Devnode 收到一个 IRP_MN_START_DEVICE
,xHCI 控制器注意到它是给它自己的 PDO 并设置一个 IoCompletionRoutine
并将它传递给 pci.sys,它将看到passed PDO 是一个child,它会将它在启动设备IRP参数中的资源列表中接收到的PnP管理器决定的MMIO物理范围分配到BAR中,并设置一个MSI-x中断在 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
和 IRP_QUERY_FILTER_RESOURCE_REQUIREMENTS
中决定并调用 IoCompleteRequest
调用 IoCompletionRoutine
xHCI 驱动程序集,它将通过 MmMapIoSpace
中的 CmResourceTypeMemory 描述符调用 [= =261=] 在 Parameters.StartDevice.AllocatedResourcesTranslated 中。它将在从 MmMapIoSpace
接收到的虚拟地址 space 中创建事件环、命令环和 DBCA,并将 MMIO space 中的寄存器设置为指向它们。然后它将事件环与在 IRP_MN_START_DEVICE
中收到的 MSI-x 矢量相关联。然后它将 。我不知道怎么办已加载 USB 集线器驱动程序,但可能是在 xHCI 驱动程序的 StartDevice
中完成的;它可以调用 IoInvalidateDeviceRelations
并说它只有一个 child,即集线器。它提供了一个带有IUSB3\
.
的DIID
尽管问题已经被回答并被标记为已接受,我还是想挥舞一下使用的旗帜:
EFI_PCI_IO_PROTOCOL
用于 PCI 操作
EFI_USB_IO_PROTOCOL
用于与 USB 设备交互,而不管主机控制器碰巧连接到什么总线。
这样您的应用程序最终可以在所有兼容的 UEFI 平台之间移植。
偶尔在这里发布答案的用户@fpmurphy 在他们的 github area.
中有这两个例子
我想通过 EFI 程序获取插入的 USB 设备的供应商 ID 和设备 ID。我可以读取整个 PCI 配置 space 我找到了我的 USB 设备插入的 USB 主机控制器 我也可以读取为该控制器寻址的整个内存,但我不知道我在这个内存中搜索的到底是什么获取这些 ID。有人可以帮助我吗?
PCI 配置space 显示 PCI 和 PCI Express 设备,而不是 USB 设备。
PCI 配置space 将显示 USB 控制器的供应商和设备 ID,但不会显示连接的设备。为此,您必须通过 reading/writing USB 寄存器枚举 USB 总线。
请注意,接管 USB 控制器会破坏 currently-running USB 堆栈并终止您的 USB 键盘和启动设备。
如果你在 UEFI shell,也许你可以在 devtree 的输出中找到你需要的东西。
如果您正在编写自己的 UEFI DXE 代码,则必须查询 USB 驱动程序。
USB 协议定义了一个设置数据包,xHCI 驱动程序必须在 xHCI 中构建该数据包,xHCI 硬件将转换为 USB – 没有像 PCIe 那样直接访问此信息的寄存器。首先,我将在 windows 上列出整个 USB 设备枚举过程,如果使用 xHCI 或 eHCI,它会有所不同。
xHCI
硬件重置后,所有根集线器端口都将处于断开状态。端口将通电并等待设备连接。当硬件检测到设备连接时,它会将 PORTSC 寄存器中的当前连接状态和连接状态更改标志设置为 1,此操作将导致 PSCEG 信号同时变高,因为它是 PORTSC 寄存器的逻辑或位。该信号在控制器中生成端口状态更改事件,这会导致 xHCI 控制器硬件将数据包(称为传输请求块)放在事件环上。事件环段和 table 从 non-paged 池中分配并在 xHCI 控制器枚举期间初始化,可能在 xHCI 驱动程序的 StartDevice
例程期间初始化,该例程在加载时调用;它还会初始化设备 MMIO 中的事件环寄存器 space。
在环上排队事件会导致硬件在 MSI-X table 中的特定偏移处触发中断(BAR 的 MSI-X 偏移量和 BAR 编号是存储在 xHCI 控制器的 PCIe 配置 space 中的 MSI-X 功能中;因此,MSI-X table 在 MMIO space 中)。主事件环总是接收所有端口状态变化事件。主要事件环总是映射到第一个 MSI-X 中断。 MSI-X 中断将作为标准 PCIe MSI 传送到 LAPIC,它将在 IRR 中为 MSI-X table 存储数据中指定的向量排队中断。 xHCI 驱动程序先前 Operational base + (400h + (10h*(n-1)))
,其中 n 是通过 MaxPorts 的端口号 1,Operational base 是 CAPLENGTH 寄存器中的值 + MMIO base。
xHCI 驱动程序通知 Root Hub 驱动程序设备已到达(我想通过调用 Root Hub callback function,它可能通过 Root Hub 的 PDO 访问),然后 Root Hub 驱动程序创建一个PDO 并通知 PnP 管理器 child 设备集已针对 Root Hub 更改。 xHCI 驱动程序自动分配插槽 ID 并在调用回调函数之前静默执行地址设备 TRB 过程并当场提供插槽 ID,或者集线器驱动程序必须通过向 xHCI 控制器发送 URB 来启动此过程以请求当通知某个端口 ID 上有端口状态更改时,分配插槽 ID 并返回给它(我不确定。而且我不确定哪些数据结构由集线器驱动程序控制,而不是 xHCI驱动程序,所以这是一个猜测)。 xHCI Driver收到URB后,会在command ring上发送enable slot TRB,并从event ring上的command completion TRB中获取slot ID;它将在 xHCI 控制器在 non-paged 池中维护的设备上下文基数组中分配一个设备上下文结构(称为输出设备上下文)和指向它的指针。在 URB 响应中,集线器驱动程序收到插槽 ID;集线器驱动程序然后在内存中分配一个输入设备上下文。 Input Device Context 中的 Input Control Context 结构中的 slot context 和 endpoint 0 context 的 Add Context 标志设置为 1 以指示它们需要添加。输入设备上下文结构中的插槽上下文然后分配有端口号、根字符串和端点数。输入上下文中的端点 0(又名默认控制)上下文数据结构必须配置为 TR 出队指针(指向它分配的传输环)、EP 类型、错误计数和最大数据包大小字段的有效值。 MaxPStreams、Max Burst Size 和 EP 状态值应设置为 0。Input Context 结构指针由集线器在地址设备命令中发送,该地址设备命令寻址到新设备的 Slot ID,通过 URB 发送到 xHCI驱动程序并将地址设备 TRB 放置在命令环上,主机控制器会将输入上下文复制到插槽的 DCBA 条目指向的输出上下文。
然后集线器驱动程序将 URB 发送到 xHCI 驱动程序以获取 fol 中的设备描述符欠款形式:
Status = SubmitRequestToRootHub(RootHubDeviceObject,
IOCTL_INTERNAL_USB_SUBMIT_URB,
Urb,
NULL);
槽ID返回到集线器后发送的所有URB都将包含槽ID。它还会 link ChildDeviceObject->DeviceExtension->UsbDeviceHandle
到 Urb->UrbHeader.UsbdDeviceHandle
,这使得 PDO 成为分配给可通过 URB 访问的新设备的集线器。 RootHubDeviceObject
是 Hub Driver 的 PDO,由 xHCI Controller Driver(或 usbxhci-usbport 对)拥有,将在本例程中调用 IoCallDriver
时使用。 URB 的类型为 GET_DESCRIPTOR
。然后用 IRP_MJ_INTERNAL_DEVICE_CONTROL
的主代码初始化 IRP,并用 StackPtr->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
作为参数之一的 URB 初始化堆栈位置。然后它在 xHCI 驱动程序拥有的 RootHubDeviceObject
上调用 IoCallDriver
。
xHCI 驱动程序将使用 URB 中指定的插槽 ID 来索引 DCBA 数组和门铃数组。它转到控制(默认,0)端点描述符,它位于 DCBA[slotID] 指向的插槽设备上下文数组中的索引 1(插槽上下文位于索引 0),并且它将写入一个设置阶段 TD(其中包含单个设置 TRB)到默认(控制)端点描述符中指定的入队指针(我假设当 xHCI 控制器处理地址设备命令时,它自动设置为与最初在输入设备上下文中的出队指针相同的地址),这是 xHCI 控制器使用 PCIe TLP 事务读取的 RAM 中的物理地址。在TRB中,指定TRB类型(Setup);传输类型(IN);传输长度(设备描述符大小 = 18);即时数据 = 0(不确定这是什么,但似乎只有设置阶段将其切换为 1);完成时中断(否); bmRequestType = 80h和bRequest = 6一起指定GET_DESCRIPTOR请求类型; wValue设置为type: Device Descriptor,即0100h;然后 wLength 为 18(设备描述符的长度)。然后它推进 Endpoint 0 Transfer ring Enqueue Pointer(将前一个 TD 的大小添加到它)。然后它在它写入的新入队指针的位置写入一个数据阶段 TD;但是,实际上,它使用了在 xHCI 枚举上通过 MMIO space 定义的 xHCI 驱动程序的虚拟地址(它在启动设备例程中的 Parameters.StartDevice.AllocatedResourcesTranslated 中的 CM_RESOURCE_LIST 上使用了 MmMapIoSpace
) 写入 RAM 中的位置,因为系统软件不能像 PCIe 设备(主机控制器)那样使用物理地址。 Data Stage TD由一个TRB组成,TRB类型设置为Data Stage TRB;方向 = 1; TRB传输长度=18;链位=0; IOC = 0(不中断,因为只有状态阶段会导致中断,即完成时);即时数据 = 0;数据缓冲区指针(xHCI 控制器将写入响应的 64 位物理地址,从集线器驱动程序提供的虚拟地址转换而来);和循环位(当前生产者循环状态(根据环绕环的排队指针切换到 1 或 0。如果生产者遇到 link TRB(它在写入之前读取),则生产者将循环位从 0 切换到 1到一个位置以确保没有 Link TRB 已经指向环的开始))。然后它再次推进 Enqueue 指针。最后它写入一个由单个状态组成的 Status Stage TD TRB with TRB type = Status Stage TRB; Direction = '0'; TRB transfer length = 0; Chain bit = 0; Interrupt On Completion = 1; Immediate data = 0; Data buffer pointer = 0(没有一个因为它是只是一个状态阶段);和循环位 =(当前生产者循环状态)。
然后 xHCI 驱动程序使用插槽 ID 索引到门铃阵列,并将序列写入该索引处的门铃寄存器,这表明控制 EP 0 排队指针已更新。然后主机控制器开始行动并读取 TRB,增加出队指针;并且,当它等于入队指针时,它停止。对于每个 TRB,它 sends the appropriate packet to the device。当它处理 Status 阶段 TRB 时,它会在 Event Ring(我认为是 ring 0)上引起中断,这会导致 MSI-x 中断,如前所述,到指定的 CPU 的 LAPIC向量,它将被 xHCI 控制器 ISR 和 DPC 拾取。 ISR 将部署完成 IRP 的 DPC。描述符将位于集线器驱动程序在 URB IRP 中指定的虚拟地址。
集线器驱动程序将它在 URB IRP 中收到的信息插入到 PDO->DeviceExtension
字段中,该字段是一个指向驱动程序定义结构的指针,它可以用它来做它想做的事情,这意味着信息本质上是已缓存,不再需要将 URB 发送到 xHCI 驱动程序。对于设备在设备描述符中报告的每个配置编号,集线器还向 xHCI 驱动程序发送 GET_CONFIGURATION 个 URB。然后 xHCI 驱动程序将该配置值传递给具有正确配置编号的 GET_CONFIGURATION TRB 中的设备,并且该配置编号的整个配置层次结构将返回到 URB 中指定地址的集线器驱动程序。然后它使用 Type 参数调用 IoInvalidateDeviceRelations()
BusRelations
的 er 和指向其由 xHCI 驱动程序分配的 PDO(物理设备 object)的指针。 PnP 管理器使用 IRP_MN_QUERY_DEVICE_RELATIONS
请求查询 PDO 的设备堆栈以获取总线上的当前设备列表;为此,它初始化一个 IRP 结构(理想情况下,根据设备 object 中的 stacksize
提示重用后备列表中的一个;否则,它直接从 non-paged 池中为一个新的)。 IRP 通过 CurrentStackLocation
成员指向堆栈(与 IRP 相邻)。然后它为它想要执行的调用初始化第一个堆栈位置(在这种情况下,IRP_MJ_PNP
的主要功能和 IRP_MN_QUERY_DEVICE_RELATIONS
的次要功能)。然后它调用发送的 PDO 的设备堆栈顶部的驱动程序,它可能是一个上层过滤器驱动程序(它不会实现那个次要功能,函数体将是向下传递它的代码——我们假设暂时没有)。因此,堆栈的顶部将是集线器的 FDO(它使用 IoGetAttachedDevice 到达,这是堆栈的顶部)。它使用 IoCallDriver(*FDO, *IRP)
调用它,它是 IofCallDriver
的包装器,它通过递减 CurrentStackLocation
指针来获取下一个堆栈位置,这导致它通过 C 的规则指向下一个堆栈位置指针算法(这是第一个堆栈位置,因为指针在它之后被初始化),然后它使用堆栈位置中指示的主函数号 IRP_MJ_PNP
索引到驱动程序的 MajorFunction
数组FDO 的 object 已传递给 IoCallDriver
(集线器驱动程序)并调用数组中该位置的函数。
该调用的代码如下所示:
return FDO->DriverObject->MajorFunction[StackPtr->MajorFunction](FDO,
Irp);
您可以看到它通过了 IRP。这允许 IRP_MJ_PNP
的 USB 集线器驱动程序的函数处理程序检查当前堆栈位置的次要函数,然后调用正确的内部函数。对于每个 child 设备,处理程序引用 DEVICE_RELATIONS 结构中的 PDO,它只是一个 PDO 指针数组。然后它将 Irp->IoStatus.Information
设置为指向数组的指针和 returns。即插即用管理器然后查看 PDO 数组并将地址与它已经枚举的设备树上的 PDO 地址进行比较。如果有新地址,它会查询设备和实例 ID 以及资源要求;并且,如果任何 PDO 已被标记为非活动,它还会使用与前面描述的相同的 IRP 初始化过程向这些 PDO 发送 IRP_MN_SURPRISE_REMOVAL
(设备的 FDO 将不会实现意外删除功能并将其传递下去到集线器驱动程序)并且集线器驱动程序将禁用设备并释放分配给它的硬件资源。
为了查询设备 ID 和实例 ID,PnP 管理器向数组中的每个 PDO 发送一个 IRP_MN_QUERY_ID
(一个用于设备 ID,另一个用于实例 ID),其指针是新的(这将是根集线器驱动程序创建的新设备的 PDO)。对于请求设备 ID 的 IRP(设备 ID 是 Windows' bus-specific 设备 ID + 供应商 ID + 子系统 ID + 子系统供应商 ID 修订的串联,它从设备和总线前缀又名. 枚举器,例如 USB),它发送一个 IRP_MN_QUERY_ID
,但将堆栈位置的 Parameters.QueryId.IdType
成员初始化为 BusQueryDeviceID
。为了响应设备 ID 查询,集线器驱动程序需要使用其总线前缀向设备查询构建和连接设备 ID 所需的信息,但它在 PDO 创建后就已经这样做了,因此它可以只使用DeviceExtension
信息被插入其中。实例 ID 是一个设备标识字符串,用于将设备与其他相同类型的设备区分开来,它可能使用 USB 描述符中的 iSerialNumber
值或一个简单的增量——它是特定于总线的。它们一起形成一个 DIID(设备实例 ID)。在 IRP 中使用 Parameters.QueryId.IdType = BusQueryInstanceID
后,PnP 管理器在单独的调用中查询 InstanceID。
集线器驱动程序通过安装在 DriverEntry 上的 IRP_MJ_PNP 处理程序接收 PnP 管理器查询的 PDO,现在使用设备描述符中的字段构建 DIID,该字段先前已解析并插入 into the DeviceExtension
of the PDO 其中 usDeviceId
似乎是已经串联的前缀 + ProductID + VendorID。请记住,DIID 是 Bus + Device ID + Instance ID,而 Device ID 是 Bus + Vendor + Product ID。然而,它对这些 ID 执行一些固定操作,将它们全部更改为 usbstor .inf 文件识别的 usbhub 标准格式。 DIID 最终将如下所示:USB\VID_<num>&PID_<num>\<InstanceID>
。只有 PDO 有 DIID 或 Hardware/Compatible ID。
PnP 管理器然后使用返回的 DIID 索引到位于 HKLM\SYSTEM\CurrentControlSet\Enum\Bus\DeviceID\InstanceID
的注册表中。
其中的 classguid 值导致 HKLM\SYSTEM\CurrentControlSet\Control\Class\<GUID>
下的 class 子项,例如,它可能是键盘 class。这些值由驱动程序.INF 文件填写。
The PnP manager checks the registry for the presence of a corresponding function driver, and when it doesn’t find one, it informs the user-mode PnP manager of the new device by its DIID. The user-mode PnP Manager first tries to perform an automatic install without user intervention. If the installation process involves the posting of dialog boxes that require user interaction and the currently logged-on user has administrator privileges, the user-mode PnP manager launches the Rundll32.exe application (the same application that hosts Control Panel utilities) to execute the Hardware Installation Wizard (%SystemRoot%\System32\Newdev.dll). If the currently logged-on user doesn’t have administrator privileges (or if no user is logged on) and the installation of the device requires user interaction, the user-mode PnP manager defers the installation until a privileged user logs on. The Hardware Installation Wizard uses Setupapi.dll and CfgMgr32.dll (configuration manager) API functions to locate INF files that correspond to drivers that are compatible with the detected device.
它通过在 .INF 文件中给它们一个 ranking by looking for Compatible IDs 来选择最相似的 .INF 文件希望与从 DIID 生成的硬件/兼容 ID 相匹配,这些 ID 被插入到设备扩展中。如果它找到一个,那么它会安装驱动程序。插入的每台新设备都会进行安装,本质上只是使用该 DIID 下的正确信息填写注册表。
Installation is performed in two steps. In the first, the third-party driver developer imports the driver package into the driver store, and in the second step, the system performs the actual installation, which is always done through the %SystemRoot%\System32\Drvinst.exe process.
控制权传回 PnP 管理器,它使用注册表项按以下顺序加载驱动程序:
- 在设备枚举键的 LowerFilters 值中指定的任何 lower-level 过滤器驱动程序。
- 在设备的 class 键的 LowerFilters 值中指定的任何 lower-level 过滤器驱动程序。
- 由设备枚举键中的服务值指定的功能驱动程序。此值被解释为 HKLM\SYSTEM\CurrentControlSet\Services. 下的驱动程序密钥
- 在设备枚举键的 UpperFilters 值中指定的任何 upper-level 过滤器驱动程序。
- 在设备 class 键的 UpperFilters 值中指定的任何 upper-level 过滤器驱动程序。
USB 驱动程序将有一个中间 devnode – 如果它是复合设备则为 usbccgp,如果它是大容量存储设备则为 usbstor,可以看到 here。当集线器驱动程序发送 DIID 时,即插即用管理器加载的是 usbstor,如上图所示。 (我们需要中间 USB 存储节点将通用 disk.sys IRP 转换为 URB 并处理特定于 USB 的驱动器配置,而不是将所有功能塞入 usbhub.sys)。
它加载驱动程序并调用每个驱动程序的 DriverEntry
函数,然后调用 AddDevice
例程,如果它们尚未 运行 (对于使用相同驱动程序的另一个 USB 设备);否则,它只调用 AddDevice
例程。 AddDevice
例程为传递的 PDO 创建一个 FDO。在其 AddDevice
例程中,过滤器驱动程序创建一个过滤器设备 Object (FiDO) 并将其附加到设备堆栈 (IoAttachDeviceToDeviceStack)。然后,PnP 管理器创建一个设备节点并将其与 PDO 相关联。 PnP 管理器在为设备 ID 发送 IRP 的同时,已经使用 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
更早地获取了总线设备(集线器驱动程序)对设备资源的意见。 PnP 管理器现在使用 IoCallDriver
在具有指定 FDO 的新设备节点顶部发送 IRP_MN_FILTER_RESOURCE_REQUIREMENTS
。只有 FDO 驱动程序处理这个,它会改变集线器驱动程序无法预测的设备 object 的任何要求。 USB 大容量存储设备不需要中断,因为它只使用主事件环。如果是,它会在对 IRP 的响应中指定 number of MSI-x messages it requires),一旦 PnP 管理器将资源分配给设备,它就会向设备堆栈发送 IRP_MN_START_DEVICE
IRP。尽管每个 USB 设备都可以有一个单独的中断和相应的事件环,但这并不重要,因为它始终是响应中断的同一最低级别驱动程序:xHCI 驱动程序; USB 设备没有要注册的 ISR。因此所有USB设备都可以共享单事件环和单中断。
在处理启动设备 IRP 的 usbstor 例程中,IRP 在例程设置 IoCompletionRoutine
后向下传递到总线设备 (usbhub)。 IoCompletionRoutine
,当它最终被调用时,将发送一个 GET_CONFIGURATION URB,该 URB 将向下传递到它的 PDO(由 usbhub 拥有)并且 usbhub 将呈现它之前缓存在该 PDO 设备扩展中的配置。 Usbstor 最终决定要使用的配置并发送 SET_CONFIGURATION URB。 Usbhub 将使用端点(即 ISOCH IN、INTERRUPT IN)填充来自缓存配置的输入上下文,并将输入上下文指针添加到 URB。 Usbhub 然后添加更多信息,如插槽 ID,并将 URB 发送到 xHCI 控制器,它拾取它并在默认端点 0 传输环上插入一个 TRB,用于主机控制器将填充插槽的输出设备上下文的设备根据输入控制上下文输入设备上下文,并通知设备有关所选配置的信息。
Usbstor 将分配一个 PDO 并调用 IoInvalidateBusRelations
。当它到达它时,usbstor 将请求存储其 PDO 的设备信息,该信息早先属于 usbhub,它将 DIID 转换为 usbstor 可识别的标准格式 disk.sys .inf 驱动程序(格式 \Disk&Ven_xxx&Prod_xxx&Rev_x.xx) 并附加 USBSTOR\ 前缀,这将允许注册表加载 disk.sys 和 partmgr.sys 作为过滤器存储在 disk.sys class 子项。 Usbstor 现在是设备的总线设备。
Disk.sys FDO 将检查维护的驱动程序 table 以查看系统中枚举了多少磁盘 (N) 并将 FDO 命名为 \Device\HarddiskN\DRN
。 Partmgr.sys 创建一个符号 link \Device\HarddiskN\Partiton0
到 \Device\HarddiskN\DRN
。然后 Partmgr 将调用 IoReadPartitionTableEx
并为每个分区创建分区 PDO,将它们命名为 \Device\HarddiskN\PartitonM
等等。对于每个分区,它向 volmg 发送一个 IOCTL_INTERNAL_VOLMGR_PARTITION_ARRIVED
IRP并向分区提供磁盘签名和偏移量。 Volmgr 为每个卷创建一个卷 PDO,并在 \Device\HarddiskN\PartitonM
和它分配给卷 PDO 的卷 PDO 名称 \Device\HarddiskvolumeX
X >= 1
之间创建 symlinks(实际上是因为这个符号link,永远无法访问具有该名称的分区 PDO,并且 PDO 本身缺少 devnode 并由 partmgr 内部管理)并向 MntMgr 发送一个 IRP_MJ_DEVICE_CONTROL
请求,通过调用指定 IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION
每个卷 PDO 的 IoBuildDeviceIoControlRequest(Harddiskvolume1 始终是 disk.sys 的 \Device\Harddisk0\DR0 上的第一个卷)。安装管理器通过查询 volmgr 以获取位于系统 object 树的设备目录(例如:“\Device\HarddiskVolume1")、为卷生成的唯一 ID 和建议的永久符号 link 名称,例如\DosDevices\D:
.
永久驱动器号和挂载点以相同的数据字段存储。值的数据称为唯一 ID,由 volmgr 以 IOCTL_MOUNTDEV_QUERY_UNIQUE_ID
提供给 mntmgr。基本磁盘的唯一 ID 是分区的签名和偏移量。如果安装管理器的数据库尚未包含与唯一 ID 配对的卷的永久驱动器号名称,则安装管理器使用建议的名称。否则,它会忽略该建议并使用其永久名称数据库中的驱动器盘符名称。它将 \DosDevices\D:
绑定到唯一 ID 并开发一个 GUID,并将 \??\Volume{GUID}
形式的 GUID 绑定到其名称中的唯一 ID space,然后创建 object 经理名称space 符号 link 之间。稍后的挂载点,即 \DosDevices\D:\mymount
也将与唯一 ID 配对。在上图中,MBR 驱动器包含 A:(系统保留)和 C: 分区,很明显它们具有相同的 MBR 签名和不同的偏移量。 D: 驱动器是 GPT 驱动器,具有 GPT 签名和偏移量。 E: 是具有 USBSTOR 特定签名和偏移量的 USB MBR 驱动器。
第一次打开C盘的文件时,不会挂载文件系统,所以在IopParseDevice
中解析文件路径字符串时调用IopCheckVpbMounted
卷(C: 对应于设备名称 \Device\HarddiskVolume2
,因为挂载管理器创建了符号 link),它将调用 IopMountDevice
,因为 VPB->DeviceObject == NULL
,它发送一个 IRP_MJ_FILE_SYSTEM_CONTROL/IRP_MN_MOUNT_VOLUME
到每个使用 IoRegisterFileSystem
注册自己的已注册文件系统注册文件系统。被调用的 FSs 处理 IRP_MN_MOUNT_VOLUME
并确定它们的文件系统是否在媒体上,如果是,文件系统创建文件系统卷设备 Object (VDO) 并将其放入 VPB。 C:
给出设备,\file
给出文件 object。 \
是根目录object。 IoGetRelatedDeviceObject
从文件 object 获取 VDO 堆栈的顶部(在 FileObject->Vpb->DeviceObject
上执行 IoGetAttachedDevice
)。
NTFS 驱动程序内部结构:
Windows USB 驱动设备树
xHCI控制器是如何枚举的
在系统启动时,pci.sys 将由 pci.sys 加载,它所做的其中一件事是调用 IoInvalidateDeviceRelations
,这会触发 PnP 管理器发送一个 IRP_MN_QUERY_DEVICE_RELATIONS
,它以 PDO 列表响应。它通过使用 MCFG
ACPI table 的 Base address of enhanced configuration mechanism
字段获取 PCIe 配置的基础 space (PCIEXBAR) 然后遍历它来当场创建列表在 4096 字节边界上,对于它在边界上找到的任何 Vendor/Device ID,它创建一个 PDO 并将其与配置编号配对。其中一个设备将是 xHCI 控制器。它经历了与上述完全相同的过程,最终创建 DIID 并检查注册表等,这将导致加载 xHCI 驱动程序; PnP 管理器还向总线询问其 child 需要的资源,并向 PDO 半堆栈提供 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
(这也将由 ACPI 总线过滤器处理)。 xHCI Driver加载完成后,会发送一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS
给xHCI Driver,这样它就可以对资源需求进行修改。 xHCI 控制器 PDO 的 Devnode 收到一个 IRP_MN_START_DEVICE
,xHCI 控制器注意到它是给它自己的 PDO 并设置一个 IoCompletionRoutine
并将它传递给 pci.sys,它将看到passed PDO 是一个child,它会将它在启动设备IRP参数中的资源列表中接收到的PnP管理器决定的MMIO物理范围分配到BAR中,并设置一个MSI-x中断在 IRP_MN_QUERY_RESOURCE_REQUIREMENTS
和 IRP_QUERY_FILTER_RESOURCE_REQUIREMENTS
中决定并调用 IoCompleteRequest
调用 IoCompletionRoutine
xHCI 驱动程序集,它将通过 MmMapIoSpace
中的 CmResourceTypeMemory 描述符调用 [= =261=] 在 Parameters.StartDevice.AllocatedResourcesTranslated 中。它将在从 MmMapIoSpace
接收到的虚拟地址 space 中创建事件环、命令环和 DBCA,并将 MMIO space 中的寄存器设置为指向它们。然后它将事件环与在 IRP_MN_START_DEVICE
中收到的 MSI-x 矢量相关联。然后它将 StartDevice
中完成的;它可以调用 IoInvalidateDeviceRelations
并说它只有一个 child,即集线器。它提供了一个带有IUSB3\
.
尽管问题已经被回答并被标记为已接受,我还是想挥舞一下使用的旗帜:
EFI_PCI_IO_PROTOCOL
用于 PCI 操作EFI_USB_IO_PROTOCOL
用于与 USB 设备交互,而不管主机控制器碰巧连接到什么总线。
这样您的应用程序最终可以在所有兼容的 UEFI 平台之间移植。
偶尔在这里发布答案的用户@fpmurphy 在他们的 github area.
中有这两个例子