基础
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