3

我想通过 EFI 程序获取插入的 USB 设备的供应商 ID 和设备 ID。我可以读取整个 PCI 配置空间我找到了我的 USB 设备被插入的 USB 主机控制器我还可以读取为此控制器寻址的整个内存,但我不知道我在这个内存中究竟在寻找什么来获取这些 ID。有人能帮我吗?

4

3 回答 3

3

PCI 配置空间显示 PCI 和 PCI Express 设备,而不是 USB 设备。

PCI 配置空间将显示 USB 控制器的供应商和设备 ID,但不显示连接的设备。为此,您必须通过读取/写入 USB 寄存器来枚举 USB 总线。

请注意,接管 USB 控制器将破坏当前运行的 USB 堆栈并终止您的 USB 键盘和启动设备。

如果你在 UEFI shell,也许你可以在 devtree 的输出中找到你需要的东西。

如果您正在编写自己的 UEFI DXE 代码,则必须查询 USB 驱动程序。

于 2016-01-21T02:19:03.863 回答
3

USB 协议定义了一个设置数据包,xHCI 驱动程序必须在 xHCI 中构建该数据包,并且 xHCI 硬件将转换为 USB——没有像 PCIe 那样直接访问此信息的寄存器。首先,我将在 windows 上布置整个 USB 设备枚举过程,如果使用 xHCI 或 eHCI,则会有所不同。

盐酸

硬件重置后,所有根集线器端口将处于断开连接状态。端口将通电并等待设备连接。当硬件检测到设备连接时,它将 PORTSC 寄存器中的当前连接状态和连接状态更改标志设置为 1,此操作将导致 PSCEG 信号变高,同时它是 PORTSC 寄存器的逻辑或位。该信号在控制器中生成端口状态更改事件,这会导致 xHCI 控制器硬件将数据包(称为传输请求块)放在事件环上。事件环段和表是从非分页池中分配的,并在 xHCI 控制器枚举期间初始化,可能在StartDevicexHCI Driver 的例程,在加载时调用;它还初始化设备 MMIO 空间中的事件环寄存器。

在环上排队事件会导致硬件在 MSI-X 表的特定偏移量处触发中断(MSI-X 与 BAR 的偏移量和 BAR 编号存储在 PCIe 配置空间中的 MSI-X 功能中) xHCI 控制器;因此,MSI-X 表位于 MMIO 空间中)。主事件环始终接收所有端口状态更改事件。主事件环总是映射到第一个 MSI-X 中断。MSI-X 中断将作为标准 PCIe MSI 传输到 LAPIC,并将在 IRR 中为 MSI-X 表存储数据中指定的向量排队中断。xHCI 驱动程序之前在 IDT 注册了 ISR使用内核和 HAL API 对应于向量的条目。xHCI Driver的ISR实现TRB包是一个Port Status Change事件;它可以评估端口 ID 以确定作为更改事件源的根集线器端口(例如 5),然后检查第 5 个 PORTSC 寄存器以查看发生了什么更改,它在距操作基础的某个偏移量处访问它,它是在引导时的 PCIe 枚举期间分配的 xHCI 控制器的 PCIe 配置空间的 BAR 中的地址的偏移量(MMIO 基础);公式为Operational base + (400h + (10h*(n-1))),其中 n 是通过 MaxPorts 的端口号 1,运算基数是 CAPLENGTH 寄存器 + MMIO 基数中的值。

xHCI 驱动程序通知 Root Hub 驱动程序设备已到达(我认为是通过调用Root Hub 回调函数, 它可能通过根集线器的 PDO 访问), 根集线器驱动程序为其创建一个 PDO 并通知 PnP 管理器该子设备集已更改为根集线器。要么 xHCI 驱动程序自动分配一个插槽 ID 并在调用回调函数之前静默执行地址设备 TRB 过程并在现场提供插槽 ID,或者集线器驱动程序必须通过向 xHCI 控制器发送一个 URB 来请求一个当通知某个端口 ID 上的端口状态发生变化时,分配并返回插槽 ID(我不确定。而且我不确定哪些数据结构由集线器驱动程序而不是 xHCI 控制司机,所以这是一个猜测)。当 xHCI Driver 收到 URB 时,它将在命令环上发送一个启用槽 TRB,并从事件环上的命令完成 TRB 中获取槽 ID;它将分配一个设备上下文结构(称为输出设备上下文)并在设备上下文基数组中指向它的指针,xHCI 控制器在非分页池中维护该结构。在 URB 响应中,集线器驱动程序接收到插槽 ID;然后集线器驱动程序在内存中分配一个输入设备上下文。输入设备上下文中的输入控制上下文结构中的槽上下文和端点 0 上下文的添加上下文标志设置为 1,以指示它们需要添加。然后为输入设备上下文结构中的槽上下文分配端口号、根字符串和端点数。端点 0(又名。默认控制)输入上下文中的上下文数据结构必须为 TR Dequeue 指针(指向它分配的传输环)、EP 类型、错误计数和最大数据包大小字段配置有效值。MaxPStreams、Max Burst Size 和 EP 状态值应设置为 0。 Input Context 结构指针由集线器在地址设备命令中发送到新设备的 Slot ID,该命令通过 URB 发送到 xHCI驱动程序并将地址设备 TRB 放在命令环上,主机控制器将输入上下文复制到插槽的 DCBA 条目指向的输出上下文。

然后,集线器驱动程序将 URB 发送到 xHCI 驱动程序以获取设备描述符,格式如下:

     Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                 IOCTL_INTERNAL_USB_SUBMIT_URB,
                                 Urb,
                                 NULL);

Slot ID 返回到 hub 后发送的所有 URB 都会包含 slot ID。它还将链接ChildDeviceObject->DeviceExtension->UsbDeviceHandleUrb->UrbHeader.UsbdDeviceHandle,这使得 PDO 成为分配给新设备的集线器,可通过 URB 访问。RootHubDeviceObject是集线器驱动程序的 PDO,它由 xHCI 控制器驱动程序(或 usbxhci-usbport 对)拥有,将IoCallDriver在此例程内部的调用中使用。URB 的类型为GET_DESCRIPTOR。然后使用主代码初始化 IRP,IRP_MJ_INTERNAL_DEVICE_CONTROL并使用作为参数之一的 URB 初始化堆栈位置 和StackPtr->Parameters.DeviceIoControl.IoControlCode = IoControlCode;。然后它调用xHCI 驱动程序拥有的IoCallDriverRootHubDeviceObject

xHCI 驱动程序将使用 URB 中指定的插槽 ID 来索引 DCBA 阵列和门铃阵列。它转到控制(默认,0)端点描述符,该描述符位于 DCBA[slotID] 指向的插槽设备上下文数组中的索引 1 处(插槽上下文位于索引 0),它将写入设置阶段 TD(其中包括单个Setup 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 驱动程序在 xHCI 枚举上的 MMIO 空间上定义的虚拟地址(它使用 这是0100h;然后 wLength 为 18(设备描述符的长度)。然后它推进 Endpoint 0 Transfer ring Enqueue Pointer(将前一个 TD 的大小加到它上面)。然后它在它写入的新入队指针的位置写入一个数据阶段 TD;但实际上,它使用 xHCI 驱动程序在 xHCI 枚举上的 MMIO 空间上定义的虚拟地址(它使用 这是0100h;然后 wLength 为 18(设备描述符的长度)。然后它推进 Endpoint 0 Transfer ring Enqueue Pointer(将前一个 TD 的大小加到它上面)。然后它在它写入的新入队指针的位置写入一个数据阶段 TD;但实际上,它使用 xHCI 驱动程序在 xHCI 枚举上的 MMIO 空间上定义的虚拟地址(它使用MmMapIoSpace在启动设备例程中的 Parameters.StartDevice.AllocatedResourcesTranslated 中的 CM_RESOURCE_LIST 上)写入 RAM 中的位置,因为系统软件不能使用与 PCIe 设备(主机控制器)不同的物理地址。Data Stage TD由一个TRB类型设置为Data Stage TRB的TRB组成;方向 = 1; TRB 传输长度 = 18;链位 = 0; IOC = 0(不中断,因为只有状态阶段会导致中断,即当它完成时);立即数据 = 0;数据缓冲区指针(xHCI 控制器将写入响应的 64 位物理地址,从集线器驱动程序提供的虚拟地址转换而来);和周期位(当前生产者周期状态(根据环绕环的入队指针切换为 1 或 0。如果生产者遇到链接 TRB,则将 Cycle Bit 从 0 切换到 1(它在写入某个位置之前读取以确保那里没有指向环开始的链接 TRB)。然后它再次推进入队指针。最后,它写入一个状态阶段 TD,该状态阶段 TD 由单个状态 TRB 组成,TRB 类型 = 状态阶段 TRB;方向='0';TRB传输长度=0;链位 = 0; 完成时中断 = 1;立即数据 = 0;数据缓冲区指针 = 0(没有,因为它只是一个状态阶段);和周期位=(当前生产者周期状态)。最后,它写入一个状态阶段 TD,该状态阶段 TD 由单个状态 TRB 组成,TRB 类型 = 状态阶段 TRB;方向='0';TRB传输长度=0;链位 = 0; 完成时中断 = 1;立即数据 = 0;数据缓冲区指针 = 0(没有,因为它只是一个状态阶段);和周期位=(当前生产者周期状态)。最后,它写入一个状态阶段 TD,该状态阶段 TD 由单个状态 TRB 组成,TRB 类型 = 状态阶段 TRB;方向='0';TRB传输长度=0;链位 = 0; 完成时中断 = 1;立即数据 = 0;数据缓冲区指针 = 0(没有,因为它只是一个状态阶段);和周期位=(当前生产者周期状态)。

然后,xHCI 驱动程序使用插槽 ID 对门铃阵列进行索引,并将序列写入该索引处的门铃寄存器,这表明控制 EP 0 入队指针已更新。然后主机控制器开始行动并读取 TRB,增加出队指针;并且,当它等于入队指针时,它会停止。对于每个 TRB,它会向设备发送适当的数据包。当它处理状态阶段 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 中指定地址的集线器驱动程序。然后它IoInvalidateDeviceRelations()使用 Type 参数调用BusRelations以及一个指向它的 PDO(物理设备对象)的指针,它是由 xHCI 驱动程序分配的。PnP 管理器使用请求查询 PDO 的设备堆栈以获取总线上的当前设备列表IRP_MN_QUERY_DEVICE_RELATIONS;为此,它初始化一个 IRP 结构(理想情况下stacksize,根据设备对象中的提示重用后备列表中的一个;否则,它直接从非分页池中为新的分配内存)。IRP 通过成员指向堆栈(与 IRP 相邻)CurrentStackLocation。然后它初始化它想要执行的调用的第一个堆栈位置(在这种情况下,一个主要函数IRP_MJ_PNP和一个次要函数IRP_MN_QUERY_DEVICE_RELATIONS)。然后它调用发送的 PDO 的设备堆栈顶部的驱动程序,这可能是一个上层过滤器驱动程序(它不会实现那个次要函数,函数体将是传递它的代码——我们假设暂时没有)。因此,堆栈的顶部将是集线器的 FDO(它使用IoGetAttachedDevice到达,它是堆栈的顶部)。它使用IoCallDriver(*FDO, *IRP)的包装器来调用它IofCallDriver,它通过递减CurrentStackLocation指针来获取下一个堆栈位置,这使得它通过 C 指针算法的规则指向下一个堆栈位置(这是指针初始化时的第一个堆栈位置)之后),然后它使用IRP_MJ_PNP堆栈位置中指示的主函数号来索引MajorFunctionFDO 的驱动程序对象的数组,它被传递给IoCallDriver(集线器驱动程序)并在数组中的该位置调用函数。

该调用的代码如下所示:

return FDO->DriverObject->MajorFunction[StackPtr->MajorFunction](FDO,
                                                             Irp);

你可以看到它通过了 IRP。这允许 USB 集线器驱动程序的函数处理程序IRP_MJ_PNP检查当前堆栈位置的次要函数,然后调用正确的内部函数。对于每个子设备,处理程序引用 DEVICE_RELATIONS 结构中的 PDO,它只是一个 PDO 指针数组。然后它设置Irp->IoStatus.Information一个指向数组的指针并返回。即插即用管理器然后查看 PDO 数组并将地址与设备树上它已经枚举的 PDO 的地址进行比较。如果有新地址,它会查询设备和实例 ID 以及资源需求;并且,如果任何 PDO 被标记为非活动状态,它还会发送一个IRP_MN_SURPRISE_REMOVAL到那些使用与前面描述的相同 IRP 初始化过程的 PDO(设备的 FDO 不会实现意外删除功能并将其传递给集线器驱动程序),集线器驱动程序将禁用设备并释放分配给它的硬件资源。

为了查询设备和实例 ID,PnP 管理器向IRP_MN_QUERY_ID数组中的每个 PDO 发送一个(一个用于设备 ID,一个单独的用于实例 ID),其指针是新的(这将是新设备的 PDO)由 Root Hub 驱动程序创建)。对于请求设备 ID 的 IRP(设备 ID 是 Windows 特定于总线的设备 ID + 供应商 ID + 子系统 ID + 子系统供应商 ID 修订版,它从设备和总线前缀(即枚举器,例如 USB)获取),它发送一个IRP_MN_QUERY_ID但初始化Parameters.QueryId.IdType堆栈位置的成员到BusQueryDeviceID. 为了响应设备 ID 查询,集线器驱动程序需要查询设备以获取使用其总线前缀构建和连接设备 ID 所需的信息,但它在创建 PDO 时就已经这样做了,因此它可以只使用DeviceExtension其中插入了信息。Instance ID 是一个设备标识字符串,用于将设备与其他相同类型的设备区分开来,它可能使用iSerialNumberUSB 描述符中的值或简单的增量——它是特定于总线的。它们一起形成一个 DIID(设备实例 ID)。Parameters.QueryId.IdType = BusQueryInstanceIDInstanceID在 IRP 中使用后由 PnP 管理器在单独的调用中查询。

集线器驱动程序通过它安装在 DriverEntry 上的 IRP_MJ_PNP 处理程序接收 PnP 管理器查询的 PDO,现在使用设备描述符中的字段构建 DIID,该字段之前已被解析并插入DeviceExtensionPDO 的似乎usDeviceId已经连接的 PDO 中前缀 + 产品 ID + 供应商 ID。请记住,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 或硬件/兼容 ID。

PnP 管理器然后使用返回的 DIID 索引到注册表中HKLM\SYSTEM\CurrentControlSet\Enum\Bus\DeviceID\InstanceID

在我自己的系统上:

其中是一个 classguid 值,该值导致 下的类子键HKLM\SYSTEM\CurrentControlSet\Control\Class\<GUID>,例如,它可能是键盘类。这些值由驱动程序 .INF 文件填充。

PnP 管理器检查注册表中是否存在相应的功能驱动程序,如果找不到,它会通过其 DIID 通知新设备的用户模式 ​​PnP 管理器。用户模式 ​​PnP 管理器首先尝试在没有用户干预的情况下执行自动安装。如果安装过程涉及发布需要用户交互的对话框并且当前登录的用户具有管理员权限,则用户模式 ​​PnP 管理器将启动 Rundll32.exe 应用程序(与控制面板实用程序相同的应用程序)来执行硬件安装向导 (%SystemRoot%\System32\Newdev.dll)。如果当前登录的用户没有管理员权限(或者没有用户登录)并且设备的安装需要用户交互,用户模式 ​​PnP 管理器将安装推迟到特权用户登录。硬件安装向导使用 Setupapi.dll 和 CfgMgr32.dll(配置管理器)API 函数来定位与与检测到的设备兼容的驱动程序对应的 INF 文件。

它通过在 .INF 文件中查找与从 DIID 生成的硬件/兼容 ID 匹配的兼容 ID(已插入设备扩展名)来给它们排名,从而选择最相似的 .INF 文件。如果找到,则安装驱动程序。插入的每个新设备都会进行安装,本质上只是在注册表中填写该 DIID 下的正确信息。

安装分两步进行。第一步,第三方驱动开发者将驱动包导入驱动存储,第二步,系统进行实际安装,始终通过%SystemRoot%\System32\Drvinst.exe进程完成。

控制权传回 PnP 管理器,它使用注册表项按以下顺序加载驱动程序:

  1. 在设备枚举键的 LowerFilters 值中指定的任何较低级别的筛选器驱动程序。
  2. 在设备类键的 LowerFilters 值中指定的任何较低级别的过滤器驱动程序。
  3. 由设备枚举键中的 Service 值指定的功能驱动程序。此值被解释为 HKLM\SYSTEM\CurrentControlSet\Services 下的驱动程序密钥。
  4. 在设备枚举键的 UpperFilters 值中指定的任何上层过滤器驱动程序。
  5. 在设备类键的 UpperFilters 值中指定的任何上层过滤器驱动程序。

USB 驱动程序将有一个中间 devnode – 如果它是复合设备,则为 usbccgp,如果它是大容量存储设备,则为 usbstor,可以在这里看到。当集线器驱动程序发送 DIID 时,即由 PnP 管理器加载的是 usbstor,如上图所示。(我们需要中间 USB 存储节点将通用 disk.sys IRP 转换为 URB 并处理 USB 特定驱动器配置,而不是将功能全部塞进 usbhub.sys 中)。

它加载驱动程序并调用DriverEntry每个函数,AddDevice如果它们尚未运行,则调用例程(对于使用相同驱动程序的另一个 USB 设备);否则,它只是调用AddDevice例程。该AddDevice例程为传递的 PDO 创建一个 FDO。在其AddDevice例程中,过滤器驱动程序创建一个过滤器设备对象 (FiDO) 并将其附加到设备堆栈 (IoAttachDeviceToDeviceStack)。然后,PnP 管理器创建一个设备节点并将其与 PDO 关联。PnP 管理器早先已经获得了总线设备(集线器驱动程序)对设备资源的意见,IRP_MN_QUERY_RESOURCE_REQUIREMENTS同时使用发送设备 ID 的 IRP。PnP 管理器现在发送一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS使用IoCallDriver在指定 FDO 的新设备节点的顶部。只有 FDO 驱动程序处理这个问题,它会改变集线器驱动程序无法预测的对设备对象的任何要求。USB 大容量存储设备不需要中断,因为它只使用主事件环。如果是这样,它会在对 IRP 的响应中指定它需要的 MSI-x 消息的数量),并且一旦 PnP 管理器将资源分配给设备,它就会发送一个IRP_MN_START_DEVICEIRP 到设备堆栈。尽管每个 USB 设备都可以有一个单独的中断和相应的事件环,但这并不重要,因为它始终是响应中断的同一个最低级别的驱动程序:xHCI 驱动程序;USB 设备没有要注册的 ISR。因此,所有 USB 设备都可以共享单个事件环和单个中断。

在处理启动设备 IRP 的 usbstor 例程中,在例程设置一个IoCompletionRoutine. 这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 将请求之前存储其属于 usbhub 的 PDO 的设备信息,并将 DIID 转换为可被 disk.sys .inf 驱动程序识别的 usbstor 的标准格式(格式 \Disk&Ven_xxx&Prod_xxx&Rev_x.xx)并附加USBSTOR\ 前缀,这将允许注册表加载 disk.sys 和 partmgr.sys 作为存储在 disk.sys 类子项中的过滤器。Usbstor 现在是该设备的总线设备。

Disk.sys FDO 将检查驱动程序维护的表,以查看系统中已枚举了多少磁盘 (N),并将命名 FDO \Device\HarddiskN\DRN。Partmgr.sys 创建一个\Device\HarddiskN\Partiton0指向\Device\HarddiskN\DRN. Partmgr 然后IoReadPartitionTableEx将为每个分区调用并创建分区 PDO,命名它们\Device\HarddiskN\PartitonM等等。对于每个分区,它会向IOCTL_INTERNAL_VOLMGR_PARTITION_ARRIVEDvolmgr 发送一个 IRP,并为该分区提供磁盘签名和偏移量。\Device\HarddiskN\PartitonMVolmgr 为每个卷创建一个卷 PDO,并在它分配给卷 PDO的卷 PDO 名称之间创建符号链接\Device\HarddiskvolumeX X >= 1(实际上由于这个符号链接,具有该名称的分区 PDO 永远无法访问,并且 PDO 本身缺少 devnode 和由 partmgr 内部管理)并向 MntMgr 发送IRP_MJ_DEVICE_CONTROL请求,指定IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION通过为每个卷 PDO 调用 IoBuildDeviceIoControlRequest(Harddiskvolume1 始终是 disk.sys 的 \Device\Harddisk0\DR0 上的第一个卷)。Mount Manager 通过向 volmgr 查询位于系统对象树的设备目录(例如:“\Device\HarddiskVolume1”)中的卷的非持久性设备对象名称(通过发送 3 个 IOCTL(控制 IRP))来响应,生成的唯一 ID对于卷和建议的持久符号链接名称,例如\DosDevices\D:.

永久驱动器号和挂载点与相同的数据字段一起存储。这些值的数据称为由 volmgr 提供给 mntmgr 的唯一 ID IOCTL_MOUNTDEV_QUERY_UNIQUE_ID。基本磁盘的唯一 ID 是分区的签名和偏移量。如果装载管理器的数据库尚未包含与唯一 ID 配对的卷的持久驱动器号名称,则装载管理器将使用建议的名称。否则,它会忽略该建议并使用其持久名称数据库中的驱动器号名称。它与\DosDevices\D:唯一 ID 关联并开发一个 GUID,并将表单\??\Volume{GUID}中的 GUID 与其名称空间中的唯一 ID 关联,然后在它们之间创建对象管理器名称空间符号链接。以后的挂载点,即\DosDevices\D:\mymount也将与唯一 ID 配对。在上图中,MBR 驱动器包含 A:(系统保留)和 C: 分区,很明显它们具有相同的 MBR 签名和不同的偏移量。D: 驱动器是 GPT 驱动器,具有 GPT 签名和偏移量。E:是具有 USBSTOR 特定签名和偏移量的 USB MBR 驱动器。

第一次打开 C: 上的文件时,不会挂载文件系统,所以在解析文件路径字符串时IopParseDevice,会调用IopCheckVpbMounted卷(C: 对应设备名\Device\HarddiskVolume2,因为创建的符号链接挂载管理器),它将调用,IopMountDevice因为VPB->DeviceObject == NULL,它向IRP_MJ_FILE_SYSTEM_CONTROL/IRP_MN_MOUNT_VOLUME每个已注册的文件系统发送一个已注册的文件系统,该文件系统使用IoRegisterFileSystem. 被调用的 FS 处理IRP_MN_MOUNT_VOLUME并确定其文件系统是否在媒体上,如果是,则文件系统创建文件系统卷设备对象 (VDO) 并将其放入 VPB。C:给出设备并\file给出文件对象。\是根目录对象。 IoGetRelatedDeviceObject从文件对象中获取 VDO 堆栈的顶部(执行IoGetAttachedDeviceFileObject->Vpb->DeviceObject)。

NTFS驱动内部结构:

Windows USB 驱动器设备树

MMIO 空间概述

如何枚举 xHCI 控制器

在系统启动时,pci.sys 将由 apci.sys 加载,它所做的一件事是 call IoInvalidateDeviceRelations,这会触发 PnP 管理器发送一个IRP_MN_QUERY_DEVICE_RELATIONS,它会以 PDO 列表对其进行响应。它通过使用MCFGACPI 表的Base address of enhanced configuration mechanism字段来获取 PCIe 配置空间 (PCIEXBAR) 的基础,然后在 4096 字节边界上迭代它,并针对在边界上找到的任何供应商/设备 ID,它在现场创建列表一个 PDO 并将其与配置号配对。其中一个设备将是 xHCI 控制器。它经过与上述完全相同的过程,最终创建一个 DIID 并检查注册表等,这将导致加载 xHCI 驱动程序;并且 PnP 管理器还向总线询问其孩子将需要的资源IRP_MN_QUERY_RESOURCE_REQUIREMENTS到 PDO 半堆栈(也将由 ACPI 总线过滤器处理)。加载 xHCI 驱动程序后,它会向 xHCI 驱动程序发送一个IRP_MN_FILTER_RESOURCE_REQUIREMENTS,以便它可以对资源要求进行修改。xHCI 控制器 PDO 的 Devnode 收到一个IRP_MN_START_DEVICE,xHCI 控制器注意到它是给自己的 PDO 并设置一个IoCompletionRoutine并将其传递给 pci.sys,它会看到传递的 PDO 是一个孩子,它会分配 MMIO物理范围由 PnP 管理器决定,它在启动设备 IRP 参数中的资源列表中接收到 BAR 中,并设置一个 MSI-x 中断,IRP_MN_QUERY_RESOURCE_REQUIREMENTS并在IRP_QUERY_FILTER_RESOURCE_REQUIREMENTS调用xHCI 驱动程序集时调用 xHCI 驱动程序IoCompleteRequestIoCompletionRoutineMmMapIoSpace在 Parameters.StartDevice.AllocatedResourcesTranslated 中的 CM_RESOURCE_LIST 中的 CmResourceTypeMemory 描述符上。它将在接收自的虚拟地址空间中创建事件环、命令环和 DBCA,MmMapIoSpace并将 MMIO 空间中的寄存器设置为指向它们。然后它将事件环与接收到的 MSI-x 向量相关联IRP_MN_START_DEVICE。然后它将使用 IoConnectInterrupt 设置 ISR 并注册 DPC。我不确定如何加载 USB 集线器驱动程序,但可能是在StartDevicexHCI 驱动程序中完成的;它可以打电话IoInvalidateDeviceRelations说它只有一个孩子,Hub。它提供了一个 DIID 后缀IUSB3\

于 2019-04-27T20:27:49.173 回答
1

尽管这个问题已经被回答并被标记为接受,但我还是想挥舞旗帜以供使用:

  • EFI_PCI_IO_PROTOCOL用于 PCI 操作
  • EFI_USB_IO_PROTOCOL用于与 USB 设备交互,无论主机控制器碰巧连接到什么总线。

这样,您的应用程序最终可以在所有兼容的 UEFI 平台之间移植。

在这里发布答案的用户 @fpmurphy 偶尔会在他们的github 区域中提供这两个示例。

于 2019-05-07T11:29:40.077 回答