EDK II 源码剖析---USB协议之EHCI(例子)一

时间:2025-01-17 14:53:56

基础

1. OHCI(Open Host Controller Interface)是支持USB1.1的标准,但它不仅仅是针对USB,还支持其他的一些接口,比如它还支持Apple的火线(Firewire,IEEE 1394)接口。与UHCI相比,OHCI的硬件复杂,硬件做的事情更多,所以实现对应的软件驱动的任务,就相对较简单。主要用于非x86的USB,如扩展卡、嵌入式开发板的USB主控。
2. UHCI(Universal Host Controller Interface),是Intel主导的对USB1.0、1.1的接口标准,与OHCI不兼容。UHCI的软件驱动的任务重,需要做得比较复杂,但可以使用较便宜、较简单的硬件的USB控制器。Intel和VIA使用UHCI,而其余的硬件提供商使用OHCI。
3. EHCI(Enhanced Host Controller Interface),是Intel主导的USB2.0的接口标准。EHCI仅提供USB2.0的高速功能,而依靠UHCI或OHCI来提供对全速(full-speed)或低速(low-speed)设备的支持。
4. XHCI(eXtensible Host Controller Interface),是最新的USB3.0的接口标准,它在速度、节能、虚拟化等方面都比前面3中有了较大的提高。xHCI 支持所有种类速度的USB设备(USB 3.0 SuperSpeed, USB 2.0 Low-, Full-, and High-speed, USB 1.1 Low- and Full-speed)。xHCI的目的是为了替换前面3中(UHCI/OHCI/EHCI)。

EDKII 中的USB 协议栈由三部分驱动程序组成:

USB 主控制器驱动,USB 总线驱动 和USB 设备驱动

其中:

USB 主控制器驱动源代码位于MdeModulePkg\Bus\Pci 目录下

USB 总线驱动和USB 设备驱动源代码位于MdeModulePkg\Bus\Usb 目录下

USB主控制器驱动(HCDI:EFI_USB2_HC_PROTOCOL

USB总线驱动(USBDI:EFI_USB_IO_PROTOCOL

USB 设备驱动

以EHCI为例:

咱们先从USB主控制器驱动说起,代码主要关注EhciDxe这个driver,这是什么driver?符合UEFI驱动模型的driver。

 在:

 

 主要看下面这个函数:

EFI_STATUS
EFIAPI
EhcDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL *This,
  IN EFI_HANDLE                  Controller,
  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath
  )
{。。。。。

 该函数逻辑如下:

 EhcDriverBindingStart开始-----打开PciIo协议,启用USB主控制器-------打开USB主控制器上的设备路径协议----保存原始的PCI属性----获取Pci设备class code-----确定设备是否为UHCI或OHCI主机控制器。如果是,则找出配套usb ehci主机控制器,并在UHCI或OHCI驱动程序连接到UHCI或OHCI主机控制器之前,强制将ehci驱动程序连接到该控制器------配套usb主机控制器的判断是否通过,如果通过,开始实例化USB2_HC_DEV并安装EFI_USB2_HC_PROTOCOL,然后就是上图的顺序了

//
  // Init EFI_USB2_HC_PROTOCOL interface and private data structure
  //
  Ehc->Signature                        = USB2_HC_DEV_SIGNATURE;

  Ehc->             = EhcGetCapability;
  Ehc->                     = EhcReset;
  Ehc->                  = EhcGetState;
  Ehc->                  = EhcSetState;
  Ehc->           = EhcControlTransfer;
  Ehc->              = EhcBulkTransfer;
  Ehc->    = EhcAsyncInterruptTransfer;
  Ehc->     = EhcSyncInterruptTransfer;
  Ehc->       = EhcIsochronousTransfer;
  Ehc->  = EhcAsyncIsochronousTransfer;
  Ehc->      = EhcGetRootHubPortStatus;
  Ehc->     = EhcSetRootHubPortFeature;
  Ehc->   = EhcClearRootHubPortFeature;
  Ehc->             = 0x2;
  Ehc->             = 0x0;

  Ehc->PciIo                 = PciIo;
  Ehc->DevicePath            = DevicePath;
  Ehc->OriginalPciAttributes = OriginalPciAttributes;

上图只讲到了中的函数,实际这里面的接口有的最终需要调用里面的函数,这里需要明白一个概念:什么是URB

URB:USB请求块,包含各种数据的信息

struct _URB {
  UINT32                          Signature;
  LIST_ENTRY                      UrbList;

  //
  // Transaction information
  //
  USB_ENDPOINT                    Ep;
  EFI_USB_DEVICE_REQUEST          *Request;     // Control transfer only
  VOID                            *RequestPhy;  // Address of the mapped request
  VOID                            *RequestMap;
  VOID                            *Data;
  UINTN                           DataLen;
  VOID                            *DataPhy;     // Address of the mapped user data
  VOID                            *DataMap;
  EFI_ASYNC_USB_TRANSFER_CALLBACK Callback;
  VOID                            *Context;

  //
  // Schedule data
  //
  EHC_QH                          *Qh;

  //
  // Transaction result
  //
  UINT32                          Result;
  UINTN                           Completed;    // completed data length
  UINT8                           DataToggle;
};

USB2_HC_DEV是Host controller的核心数据结构,在初始化过程中创建;QTD、QH的数据结构的定义位于 EHCI spec 3.5/3.6;

2.管理controller和bulk传输:插入Asynchronous Schedule list

//把组装好的Qh插入EHCI主控制器的Asynchronous Schedule List,以便硬件执行传输命令
  EhcLinkQhToAsync (Ehc, Urb->Qh);
//阻塞式的执行此次controller传输
  Status = EhcExecTransfer (Ehc, Urb, TimeOut);
//从Asynchronous Schedule List中将其移除
  EhcUnlinkQhFromAsync (Ehc, Urb->Qh);

3.管理isochronous和interrupt传输:插入Periodic schedule frame list

//把组装好的Qh插入EHCI主控制器的Periodic schedule frame list,以便硬件执行传输命令
  EhcLinkQhToPeriod (Ehc, Urb->Qh);
//并把URB插入异步中断传输链表 &Ehc->AsyncIntTransfers
  InsertHeadList (&Ehc->AsyncIntTransfers, &Urb->UrbList);

4.插入硬件链表的URB,硬件会自动执行发送;

5.链表&Ehc->AsyncIntTransfers是由驱动程序创建并管理的,由EhcMonitorAsyncRequests()管理;

(1)他会循环&Ehc->AsyncIntTransfers上的每个urb;

(2)通过判断来判断执行结果(一个urb中包含一个QH和一串QTD);

(3)更新QH,为下一轮异步传输准备;

(4)如果有回调函数,执行回调函数。

着重讲一下:阻塞式的执行此次controller传输:Status = EhcExecTransfer (Ehc, Urb, TimeOut);

EFI_STATUS
EhcExecTransfer (
  IN  USB2_HC_DEV         *Ehc,
  IN  URB                 *Urb,
  IN  UINTN               TimeOut
  )
{
  EFI_STATUS              Status;
  UINTN                   Index;
  UINTN                   Loop;
  BOOLEAN                 Finished;
  BOOLEAN                 InfiniteLoop;

  Status       = EFI_SUCCESS;
  Loop         = TimeOut * EHC_1_MILLISECOND;
  Finished     = FALSE;
  InfiniteLoop = FALSE;

  //
  // According to UEFI spec section 16.2.4, If Timeout is 0, then the caller
  // must wait for the function to be completed until EFI_SUCCESS or EFI_DEVICE_ERROR
  // is returned.
  //
  if (TimeOut == 0) {
    InfiniteLoop = TRUE;
  }

  for (Index = 0; InfiniteLoop || (Index < Loop); Index++) {
    Finished = EhcCheckUrbResult (Ehc, Urb);

    if (Finished) {
      break;
    }

    gBS->Stall (EHC_1_MICROSECOND);
  }

  if (!Finished) {
    DEBUG ((EFI_D_ERROR, "EhcExecTransfer: transfer not finished in %dms\n", (UINT32)TimeOut));
    EhcDumpQh (Urb->Qh, NULL, FALSE);

    Status = EFI_TIMEOUT;

  } else if (Urb->Result != EFI_USB_NOERROR) {
    DEBUG ((EFI_D_ERROR, "EhcExecTransfer: transfer failed with %x\n", Urb->Result));
    EhcDumpQh (Urb->Qh, NULL, FALSE);

    Status = EFI_DEVICE_ERROR;
  }

  return Status;
}

这个函数如果设备出错了咋办?现象就是会一直去重试,达到最大重试次数,如果设备依然出错,那么直接跳过,但是有个问题,达到最大重试次数一般得好几分钟,这就容易造成不能进系统的假象,得等到重试完成,这种情况一般出现在工控机中,由于外界USB设备较多,不排除USB设备异常。

ok,下一节再分析USB device