从PCI配置空间(EFI)读取USB设备供应商ID和设备ID

问题描述 投票:0回答:3

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

config space pci uefi
3个回答
1
投票

@ myron-semack是正确的,但更具体地说,USB协议定义了一个设置数据包,xHCI驱动程序必须将其发送到设备 - 它没有直接访问此信息的寄存器,如PCIe。我在这里详述;但首先,我将在Windows上列出整个USB设备枚举程序,如果使用xHCI或eHCI则会有所不同。其中一些是我所知,所以它可能不完全准确。 (稍后我会用eHCI更新我的答案)。

的xHCI

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

enter image description here

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

xHCI驱动程序通知根集线器驱动程序设备已到达(我想通过调用Root Hub callback function,它可以通过根集线器的PDO访问),根集线器驱动程序为它创建一个PDO并通知PnP管理器Root Hub的子设备集已更改。 xHCI驱动程序已经为设备分配了插槽ID和结构,或者集线器驱动程序必须通过在被通知某个端口状态发生变化时向具有选定插槽ID的xHCI控制器发送URB来启动此操作。端口ID(我不确定,但鉴于插槽ID是集线器抽象,后者才有意义)。当xHCI驱动程序收到分配插槽ID的URB时,它将在设备上下文基本阵列中分配设备上下文结构和指针,xHCI控制器在非页面缓冲池中维护该结构。在设备上下文结构(称为输出设备上下文)中,它分配一个Slot Context结构和一个Default Endpoint结构,该结构指向它现在为端点0分配的传输环。然后它分配一个输入设备上下文。输入上下文中的输入控制上下文结构中的插槽上下文和端点0上下文的添加上下文标志设置为1以指示需要添加它们。输入上下文中的端点0上下文数据结构必须配置TR出队指针,EP类型,错误计数和最大数据包大小字段的有效值。 MaxPStreams,Max Burst Size和EP状态值应设置为0.集线器引用寻址到新设备的Slot ID的地址设备命令中的输入上下文结构,该命令通过URB发送到XHCI驱动程序;最后,主机控制器将输入上下文复制到DCBA条目指向的输出上下文。然后,集线器驱动程序将URB发送到主机控制器以获取以下形式的设备描述符:

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

URB将使用集线器刚刚分配给设备的插槽ID构建,它还将ChildDeviceObject->DeviceExtension->UsbDeviceHandle链接到Urb->UrbHeader.UsbdDeviceHandle,这使得设备的PDO可通过URB访问。 RootHubDeviceObject是集线器驱动程序的PDO,它由xHCI控制器驱动程序(或usbxhci-usbport对)拥有,它将在此例程中用于调用IoCallDriver。 URB将是GET_DESCRIPTOR类型。然后使用主要代码IRP_MJ_INTERNAL_DEVICE_CONTROL初始化IRP,并使用URB作为参数和StackPtr->Parameters.DeviceIoControl.IoControlCode = IoControlCode;之一初始化堆栈位置。然后它在IoCallDriver上调用RootHubDeviceObject,后者由xHCI Driver拥有。

xHCI控制器将使用URB中指定的Slot ID来索引DCBA阵列和门铃阵列。它转到Control(默认,0)端点描述符,该描述符位于DCBA [slotID]指向的设备上下文数组中的索引1处,它将向Enqueue写入设置阶段TD(由单个Setup TRB组成)控制端点描述符中指定的指针,它是RAM中的物理地址,xHCI控制器使用PCIe TLP事务读取该地址。在TRB中,它指定TRB类型(Setup);传输类型(IN),传输长度(设备描述符大小= 18);立即数据= 0(不确定这是什么,但只有设置阶段似乎将其切换为1);中断完成(否); bmRequestType = 80h和bRequest = 6一起指定GET_DESCRIPTOR请求类型; wValue设置为:Device Descriptor,为0100h;然后wLength是18(设备描述符的长度)。然后它推进Endpoint 0 Transfer ring Enqueue Pointer(将前一个TD的大小添加到它)。然后它将数据阶段TD写入它编写的新Enqueue指针的位置;但实际上,它使用xHCI驱动程序在xHCI枚举上定义的xHCI驱动程序的虚拟地址,使用MmMapIoSpace写入RAM中的位置,因为系统软件不能使用物理地址,这与PCIe设备(主机控制器)不同。数据阶段TD由一个TRB类型设置为数据阶段TRB的TRB组成;方向= 1; TRB转移长度= 18;链位= 0; IOC = 0(不会中断,因为只有状态阶段会导致中断,即完成时);立即数据= 0;数据缓冲区指针(xHCI控制器将响应写入的64位物理地址);和循环位(当前生成器循环状态(基于环绕指针环绕的指针切换为1或0。如果生成器遇到链接TRB,它会在从写入一个位置之前读取,将循环位从0切换到1)确保没有指向环的开始的Link TRB))。然后再次推进Enqueue指针。最后它写入一个状态阶段TD,由TRB类型=状态阶段TRB的单个状态TRB组成; Direction ='0'; TRB传输长度= 0;链位= 0;中断完成= 1;立即数据= 0;数据缓冲区指针= 0(没有一个,因为它只是一个状态阶段);和循环bit =(当前生产者周期状态)。

enter image description here

然后,xHCI驱动程序使用Slot ID索引到Doorbell数组,并将序列写入该索引处的门铃寄存器,这表示Control EP 0 Enqueue Pointer已更新。然后主机控制器开始行动并读取TRB,递增出队指针;并且,当它等于Enqueue指针时,它会停止。当它处理状态级TRB时,它将在事件环上产生一个中断(我认为是0),这会导致MSI-x中断(如前所述)到CPU的LAPIC到指定的向量,这将被选中由xHCI Contoller ISR和DPC提升。 ISR将部署一个DPC,它将完成IRP,并可能将描述符放在虚拟地址对应于它在集线器驱动程序在URB IRP中指定的TRB中设置的物理地址。

集线器驱动程序将它在URB IRP中收到的信息插入到PDO->DeviceExtension字段中,该字段是指向驱动程序定义结构的指针,它可以根据需要执行操作,这意味着信息基本上被缓存,不再需要发送URB到xHCI驱动程序。然后它使用IoInvalidateDeviceRelations()的Type参数调用BusRelations,并指向它由主机控制器分配的PDO(物理设备对象)。 PnP管理器使用IRP_MN_QUERY_DEVICE_RELATIONS请求查询PDO的设备堆栈以获取总线上的当前设备列表;为此,它初始化一个IRP结构(理想情况下,根据设备对象中的stacksize提示重新使用旁视列表中的一个;否则,它会直接从非分页池中为新的内存分配内存)。 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的驱动程序对象的IoCallDriver数组在数组中。

该调用的代码如下所示:

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

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

为了查询设备和实例ID,PnP管理器向阵列中的每个PDO发送一个IRP_MN_SURPRISE_REMOVAL(一个用于设备ID,一个单独的一个表单实例ID),它接收的指针是新的(这将是新设备的PDO)由根集线器驱动程序创建的)。对于请求设备ID的IRP(设备ID是Windows从设备获取的设备+供应商ID的串联以及总线特定前缀,例如USB),它发送IRP_MN_QUERY_ID但是将堆栈位置的IRP_MN_QUERY_ID成员初始化为Parameters.QueryId.IdType。在响应设备ID查询时,集线器驱动程序需要查询设备以获取构建和连接设备ID所需的信息,但是一旦PDO创建就已经这样做了,所以它只能使用BusQueryDeviceID,其中信息已插入。实例ID是设备标识字符串,用于区分设备与相同类型的其他设备,并且可能在USB描述符中使用DeviceExtension值。它们一起形成DIID(设备实例ID)。在IRP中使用iSerialNumber后,PnP管理器在单独的调用中查询InstanceID。

集线器驱动程序通过安装在DriverEntry上的IRP_MJ_PNP接收PDO地址,现在使用设备描述符中的字段构建DIID,该字段先前被解析并插入Parameters.QueryId.IdType = BusQueryInstanceID,其中into the DeviceExtension of the PDO似乎是已连接的前缀+ ProductID + VendorID。请记住,DIID是总线前缀+设备ID +实例ID,设备ID是供应商+产品ID。它最终会看起来像这样:> usDeviceId

然后,它使用DIID索引到HKLM \ SYSTEM \ CurrentControlSet \ Enum \ Bus \ DeviceID \ InstanceID中的注册表。

在我自己的系统上:USB\VID_<num>&PID_<num>\<InstanceID>

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

一些驱动程序将有一个中间devnode - usbccgp如果它是一个复合设备而usbstor如果它是一个大容量存储设备,可以看到enter image description here。当集线器驱动程序发送DIID时,它是usbstor由PnP管理器加载,然后usbstor将在其here例程中分配一个PDO并调用StartDevice。当它到达它时,usbstor将使用带有USBSTOR \前缀的DIID进行响应,这将允许注册表加载disk.sys。 Usbstor现在是该设备的总线设备。

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

它通过在希望与DIID匹配的.INF文件中查找IoInvalidateBusRelations来选择最接近类似的.INF文件,方法是给它们一个ranking。如果找到,则安装驱动程序。插入的每个新设备都会进行安装,并且基本上只是在该DIID下填入注册表中的正确信息。

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

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

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

它加载驱动程序并调用每个驱动程序的Compatible IDs函数,然后调用DriverEntry例程(如果它们尚未运行)(对于另一个使用相同驱动程序的USB设备);否则,它只是调用AddDevice例程。 AddDevice例程为传递的PDO创建FDO。在其AddDevice例程中,过滤器驱动程序创建一个过滤器设备对象(FiDO)并将其附加到设备堆栈(IoAttachDeviceToDeviceStack)。然后,PnP管理器创建设备节点并将其与PDO关联。 PnP管理器早先已经使用AddDevice在为设备ID发送IRP的同时获取了有关设备资源的总线设备(集线器驱动程序,Usbstor或Usbccgp,取决于设备)的意见。 PnP管理器现在使用指定FDO的新设备节点顶部的IRP_MN_QUERY_RESOURCE_REQUIREMENTS发送IRP_MN_FILTER_RESOURCE_REQUIREMENTS。只有FDO驱动程序才能处理此问题,它将改变集线器驱动程序无法预测所需的设备对象的任何要求。例如,USB大容量存储设备需要一个中断。它将指定在对该IRP的响应中(即IoCallDriver),并且一旦PnP管理器将资源分配给设备,它就会向设备堆栈发送number of MSI-x messages it requires IRP。

在处理启动设备IRP的例程中,IRP_MN_START_DEVICE。因为它指定了一个MSI-x中断,它现在将IRP发送到集线器驱动程序(或拥有PDO的驱动程序,可能是usbstor.sys)。我想它会说'我想要设置一个中断,并且我已经被PnP管理器给了这个向量'并在IRP中指定了PDO,它将执行以及发送与Configure相对应的IRP。端点命令(SET_CONFIGURATION),它将在设备上下文中设置端点,即ISOCH IN,INTERRUPT IN(为此,它可能在一个IRP中执行,并且在IRP中包含指向xHCI驱动程序将使用的输入上下文的指针构造一个配置端点TRB和要使用的配置号。集线器驱动程序使用PDO地址进行拾取和修改,并插入Slot ID等信息(除非xHCI控制器在之前通过the driver will set up the interrupts required using IoConnectInterrupt and register DPCs链接它时更早地记录了PDO地址;因此可能不需要,但我对此表示怀疑因为它是集线器驱动程序的责任,它维护整洁的抽象层)并使请求更具体。然后它将URB发送到xHCI控制器并将其拾取并创建事件环并注册MSI-x中断向量。或者,这可能是在xHCI驱动程序和PCI驱动程序首先分别获取UsbDeviceHandle时设置的,在设置IRP_MN_START_DEVICE后设备本身会传递下来。在发送配置端点URB之前,它需要读取配置和接口描述符以决定使用哪个配置,这将是它发送到集线器驱动程序的另一个IRP,它将响应它在PDO到达通知时复制的配置。第一个进入子设备的PDO扩展。该信息最初将由集线器驱动程序检索,该驱动程序将GET_DESCRIPTOR URB发送到xHCI驱动程序,以获取设备最初在针对默认控制端点传输环的描述符中报告的每个配置编号。然后,xHCI驱动程序将该配置值传递给具有正确配置编号的GET_CONFIGURATION TRB中的设备,并且该配置编号的整个配置层次结构将返回到URB中指定的地址处的集线器驱动程序。配置端点URB将指定要设置的配置号,它将由集线器驱动程序拾取,它可能通过插入指向设备驱动程序所选的相应配置的指针来修改URB。 xHCI驱动程序可能会使用设备配置中指定的端点填充输入设备上下文并分配适当的结构,然后发送SET_CONFIGURATION TRB,主机控制器将根据输入将输入设备上下文填充到输出设备上下文控制上下文并通知设备所选配置。

现在设备已准备好发送数据。

MMIO空间概述IoCompletionRoutine如何枚举xHCI控制器?

在系统启动时,pci.sys将被加载,其中的一件事就是调用enter image description here,它会触发PnP管理器发送一个IoInvalidateDeviceRelations,它会以PDO列表响应。它通过使用MCFG ACPI表作为PCIe配置空间PCIEXBAR的基础,然后在4096字节边界上迭代它,并在边界上找到的任何供应商/设备ID,在现场创建列表,它创建一个PDO和将它与配置号配对。其中一个设备是xHCI控制器。它完成与上述完全相同的过程,最终创建DIID并检查注册表等,这将导致xHCI驱动程序被加载;并且PnP经理还要求公共汽车提供其孩子用IRP_MN_QUERY_DEVICE_RELATIONS所需的资源。加载xHCI驱动程序后,它会向xHCI驱动程序发送IRP_MN_QUERY_RESOURCE_REQUIREMENTS,以便它可以修改资源要求。 xHCI控制器PDO的Devnode接收IRP_MN_FILTER_RESOURCE_REQUIREMENTS,xHCI控制器注意到它是用于自己的PDO并设置IRP_MN_START_DEVICE并将其传递给pci.sys,这将看到传递的PDO是一个子节点,它将分配它在IRP资源列表中收到的MMIO物理范围进入BAR并调用IoCompletionRoutine xHCI驱动程序集,它将调用IoCompletionRoutine而不是MMIO赋值。它将在从MmMapIoSpace接收的虚拟地址空间中创建事件环,命令环,DBCA,并将MMIO空间中的寄存器设置为指向它们。我不确定如何加载USB集线器驱动程序,但可能是在xHCI驱动程序的MmMapIoSpace中完成的;它可以打电话给StartDevice并说它只有一个孩子,Hub。 xHCI驱动程序还需要访问PCIe配置空间以设置指向MSI-x表的指针;这可以通过pci.sys本身在处理IoInvalidateDeviceRelations时完成,但也可以通过xHCI控制器向pci.sys发送IRP来完成,要求它为xHCI控制器的PDO设置MSI-x中断。


3
投票

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

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

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

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

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


1
投票

尽管这个问题已经被回答并被标记为已被接受,但我还想挥动一个标志以便使用:

  • IRP_MN_START_DEVICE用于PCI操作
  • EFI_PCI_IO_PROTOCOL用于与USB设备交互,无论主机控制器恰好连接到哪个总线。

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

在这里发布答案的用户@fpmurphy偶尔会在他们的EFI_USB_IO_PROTOCOL中有两个例子。

© www.soinside.com 2019 - 2024. All rights reserved.