(1)几种USB控制器类型
在UEFI中,usb相关的驱动有
MdeModulePkg/Bus/Pci/OhciDxe/OhciDxe.inf
MdeModulePkg/Bus/Pci/UhciDxe/UhciDxe.inf
MdeModulePkg/Bus/Pci/EhciDxe/EhciDxe.inf
MdeModulePkg/Bus/Pci/XhciDxe/XhciDxe.inf
MdeModulePkg/Bus/Usb/UsbBusDxe/UsbBusDxe.inf
MdeModulePkg/Bus/Usb/UsbKbDxe/UsbKbDxe.inf
MdeModulePkg/Bus/Usb/UsbMassStorageDxe/UsbMassStorageDxe.inf
前四个是和具体的USB控制器相关的驱动,后面三个是协议层的实现。
Ohci:(open host controller interface)是支持USB1.1协议标准的,但是它不仅仅针对USB设备,还支持Apple的火线(firewire IEEE1394)接口。主要用于非x86的USB设备,如嵌入式设备的USB控制器。
Uhci:(Universall host controller interface)是Intel主导的对USB1.0 1.1接口标准。与Ohci不兼容。Intel的VIA使用的就是uhci,和ohci相比uhci硬件设计比较简单,对应的软件驱动的任务比较重,实现的复杂。ohci硬件设计的比较复杂,软件驱动的任务就比较简单一些。所以ohci控制器的硬件成本比较高一些。
Ehci(Ehhanced host controller interface)是Intel主导的USB2.0协议标准,Ehci仅仅提供USB2.0的高速功能,而依靠Uhci或者Ohci来提供对全速或者低速设备的支持。(Xhci同样如此,只提供高速功能)
Xhci:(extensible host controller interface)是最新的USB3.0协议接口标准,在速度和节能,虚拟化等方面均有较大的提升。支持各种速度的usb设备,对前面的2.0 1.0设备都兼容。USB3.0的驱动同样可以驱2.0的接口控制器。
目前大部分键盘鼠标的控制器使用的是Ohci或者Uhci,而U盘等高速设备使用的是Ehci或者Xhci
(2)热插拔实现的原理
硬件设计:
在最初的标准里,USB接头有4条线:电源,D-,D+,地线。后来OTG出现了,又增加了miniUSB接头。而miniUSB接头则有5条线,多了一条ID线,用来标识身份用的。标准USB口只有A型和B型。其中每一型又分为插头和插座,例如A型插头,A型插座等。我们平常电脑上用的那种插座叫做A型USB插座,而相应的插头,叫做A型插头,例如U盘上那种。而像打印机上面那个插座,则是B型插座(比较四方的,没电脑上面那种扁),相应的插头,就是B型插头。也许你见过一头方一头扁的USB延长线,没错了,扁的那头就叫做A型插头,而方的那头,就叫做B型插头,而相应的被插的那两个插座,就分别是A型插座和B型插座了。A型插头是插不进B型插座的,反之亦然。
miniUSB也分为A型,B型,但增加了一个AB型(不是血型呀,别搞错了,没有O型^_^)。既然它叫做miniUSB,那么当然它就是很小的了,主要是给便携式设备用的,例如MP3、手机、数码相机等。USB是一主多从结构,即一个时刻只能有一台主机。像PC机就是一个主机,其它的只能是设备,因而两个设备之间是无法直接进行通信的。而USB OTG(on the go)的出现,则解决了这个矛盾:一个设备可以在某种场合下,改变身份,以主机的形式出现。因而就出现了AB型的miniUSB插座,不管是A型miniUSB插头,还是B型miniUSB插头,都可以插进去,而靠里面多出的那条ID线来识别它的身份:是主机还是从机。这样两个USB设备就可以直接连接起来,进行数据传送了。 像我们MP3上用的那中miniUSB插座,就是B型的miniUSB插座(注意,有一类miniUSB插座,似乎不是USB规范里面的,因为miniUSB接头应该有5条线,而这种插座只有4条线)。由于USB是支持热插拔的,因此它在接头的设计上也有相应的措施。USB插头的地引脚和电源引脚比较长,而两个数据引脚则比较短,这样在插入到插座中时,首先接通电源和地,然后再接通两个数据线。这样就可以保证电源在数据线之前接通,防止闩锁发生。至于USB电缆,通常我们不怎么关心,买现成的就行了,除非你是生产USB线缆的。在全速模式下需要使用带屏蔽的双绞电缆线,而低速模式模式则可以不用屏蔽和双绞。此外,USB协议规定,USB低速电缆长度不得超过3米,而全速电缆长度不得超过5米。这是因为线缆传输有延迟,要保证能够正确响应,就不能延迟太多。USB标准规定了里面信号线的颜色,其中Vbus为红色,D-为白色,D+为绿色,GND为黑色。然而,我见过很多USB线缆并没有遵循标准,所以大家在使用时要小心,用表测量一下比较可靠。
当有一个USB设备插入到集线器或者usb插头上的时候,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻。对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。
(2)实现热插拔的软件机制(如何检测port是否有优盘插入)
在uefi的公共模块中UsbBusDxe中,USBBusControllerDriverStart函数,如果依赖的Protocolg如EfiDevicePathProtocolGuid已经安装,然后检测UsbBusId Protocol是否安装,如果没有安装,执行UsbBusBuildProtocol这个函数,这个函数主要初始化USB_BUS这样一个变量,来初始化USBBUS。(UsbBus Protocol是依赖与PCIBUS的,pci又依赖于PCI Host Bridege,他们之间的安装关系如下:HostBride->PciBus->UsbBus->Xhci/Ehci)
UsbBus层的主要代码如下:
UsbBus = AllocateZeroPool (sizeof (USB_BUS));USB_BUS结构体的成员如下
};
初始化之后,判断usb bus所依赖的Protocol是否已经安装,这里判断的是xhci 和ehci的驱动,如果有一个已经成功安装,就进行安装USB busProtocol,代码如下
Status = gBS->InstallProtocolInterface (
&Controller,
&gEfiCallerIdGuid,
EFI_NATIVE_INTERFACE,
&UsbBus->BusId
);
然后成功安装之后,为usb 设备创建一个RootHub device 代码如下
RootHub = AllocateZeroPool (sizeof (USB_DEVICE));这个USB_DEVICE结构体如下
struct _USB_DEVICE {
USB_BUS *Bus;
//
// Configuration information
//
UINT8 Speed;
UINT8 Address;
UINT32 MaxPacket0;
//
// The device's descriptors and its configuration
//
USB_DEVICE_DESC *DevDesc;
USB_CONFIG_DESC *ActiveConfig;
UINT16 LangId [USB_MAX_LANG_ID];
UINT16 TotalLangId;
UINT8 NumOfInterface;
USB_INTERFACE *Interfaces [USB_MAX_INTERFACE];
//
// Parent child relationship
//
EFI_USB2_HC_TRANSACTION_TRANSLATOR Translator;
UINT8 ParentAddr;
USB_INTERFACE *ParentIf;
UINT8 ParentPort; // Start at 0
UINT8 Tier;
BOOLEAN DisconnectFail;
};
然后为这个变量中的成员进行初始化,然后调用Status = mUsbRootHubApi.Init (RootIf);这里mUsbRootHubApi是一个表
具体内容如下:
USB_HUB_API mUsbRootHubApi = {
UsbRootHubInit,
UsbRootHubGetPortStatus,
UsbRootHubClearPortChange,
UsbRootHubSetPortFeature,
UsbRootHubClearPortFeature,
UsbRootHubResetPort,
UsbRootHubRelease
};
USB_HUB_API 的类型定义如下:
struct _USB_HUB_API{
USB_HUB_INIT Init;
USB_HUB_GET_PORT_STATUS GetPortStatus;
USB_HUB_CLEAR_PORT_CHANGE ClearPortChange;
USB_HUB_SET_PORT_FEATURE SetPortFeature;
USB_HUB_CLEAR_PORT_FEATURE ClearPortFeature;
USB_HUB_RESET_PORT ResetPort;
USB_HUB_RELEASE Release;
};
这里面就是注册的一些回调函数的地址,上面的代码执行的就是第一个函数UsbRootHubInit,来进行RootHub的初始化。
在这个UsbRootHubInit函数中,函数的入参为USB_INTERFACE类型的变量指针,也就是上面的函数入参的地址。然后对这变量进行赋值。
USB_INTERFACE 的定义如下:
struct _USB_INTERFACE {
UINTN Signature;
USB_DEVICE *Device;
USB_INTERFACE_DESC *IfDesc;
USB_INTERFACE_SETTING *IfSetting;
//
// Handles and protocols
//
EFI_HANDLE Handle;
EFI_USB_IO_PROTOCOL UsbIo;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
BOOLEAN IsManaged;
//
// Hub device special data
//
BOOLEAN IsHub;
USB_HUB_API *HubApi;
UINT8 NumOfPort;
EFI_EVENT HubNotify;
//
// Data used only by normal hub devices
//
USB_ENDPOINT_DESC *HubEp;
UINT8 *ChangeMap;
//
// Data used only by root hub to hand over device to
// companion UHCI driver if low/full speed devices are
// connected to EHCI.
//
UINT8 MaxSpeed;
};
初始化代码中,值得注意的一个函数就是HubIf->HubApi = &mUsbRootHubApi;其中mUsbRootHubApi是一个表,表中注册了UsbRootHub需要使用的函数,这里也是以回调的方式实现的。
然后创建了一个事件,事件在uefi中,就是相当于中断的实现方式,这里创建的事件是定时器触发的,每隔一段事件触发一次这个事件,来执行事件注册的回调函数。而这个回调函数,就是检测usb 端口的状态变化的函数,如果端口状态发生变化就会执行对应的操作函数(插入:执行xhci/ehci驱动的安装。拔出:卸载驱动更新状态寄存器的值)。具体代码如下
//
// Create a timer to poll root hub ports periodically
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
UsbRootHubEnumeration,
HubIf,
&HubIf->HubNotify
);
if (EFI_ERROR (Status)) {
return Status;
}
gBS->SignalEvent (HubIf->HubNotify);
Status = gBS->SetTimer (
HubIf->HubNotify,
TimerPeriodic,
5*USB_ROOTHUB_POLL_INTERVAL// determine UsbRootHubEnumeration function execulate poll time
);
if (EFI_ERROR (Status)) {
gBS->CloseEvent (HubIf->HubNotify);
}
return Status;
}
注册的检测端口状态变化的函数是UsbRootHubEnumeration,我们这里设置了500ms检测一次。
UsbRootHubEnumeration函数详解:
函数的代码如下:
VOID
EFIAPI
UsbRootHubEnumeration (
IN EFI_EVENT Event,
IN VOID *Context
)
{
USB_INTERFACE *RootHub;
UINT8 Index;
USB_DEVICE *Child;
RootHub = (USB_INTERFACE *) Context;
for (Index = 0; Index < RootHub->NumOfPort; Index++)
{
Child = UsbFindChild (RootHub, Index);
if ((Child != NULL) && (Child->DisconnectFail == TRUE))
{
DEBUG (( EFI_D_INFO, "UsbEnumeratePort: The device disconnect fails at port %d from root hub %p, try again\n", Index,RootHub));
UsbRemoveDevice (Child);
}
UsbEnumeratePort (RootHub, Index);
}
}
进入函数内部后(这个函数的入参其实就是&HubIf->HubNotify和HubIf),根据函数的第二个参数,将其转化为USB_INTERFACE类型的变量,然后根据RootHub中的port个数依次遍历而每一个口的状态。Child = UsbFindChild (RootHub, Index);这个函数是依次遍历RootHub下面的每一个device,如果这个设备的DisconnectFail置位表示这个设备需要移除,就执行移除设备的函数UsbRemoveDevice (Child);然后依次枚举每一个port,执行函数 UsbEnumeratePort (RootHub, Index);(这个函数里面执行了驱动的安装程序,插入u盘后,usb设备是如何开始工作的都从代码中有体现)。
UsbEnumeratePort (RootHub, Index);函数详解:
<1>Status = HubApi->GetPortStatus (HubIf, Port, &PortState);
首先获取端口的状态,这里面执行的是UsbRootHubGetPortStatus这个函数,这个函数内部又调用的Status = UsbHcGetRootHubPortStatus (Bus, Port, PortState);这个函数,在这个函数内部会根据xhci的驱动是否安装来调用xhci中的获取port status的函数还是执行ehci中获取端口状态的函数,这里执行的是xhci中的获取端口状态的函数。代码实现如下:
EFI_STATUS
UsbHcGetRootHubPortStatus (
IN USB_BUS *UsbBus,
IN UINT8 PortIndex,
OUT EFI_USB_PORT_STATUS *PortStatus
)
{
EFI_STATUS Status;
if (UsbBus->Usb2Hc != NULL) {
Status = UsbBus->Usb2Hc->GetRootHubPortStatus (UsbBus->Usb2Hc, PortIndex, PortStatus);
} else {
Status = UsbBus->UsbHc->GetRootHubPortStatus (UsbBus->UsbHc, PortIndex, PortStatus);
}
return Status;
}这里UsbBus->Usb2Hc就是EFI_USB2_HC_PROTOCOL gXhciUsb2HcTemplate = {
XhcGetCapability,
XhcReset,
XhcGetState,
XhcSetState,
XhcControlTransfer,
XhcBulkTransfer,
XhcAsyncInterruptTransfer,
XhcSyncInterruptTransfer,
XhcIsochronousTransfer,
XhcAsyncIsochronousTransfer,
XhcGetRootHubPortStatus,
XhcSetRootHubPortFeature,
XhcClearRootHubPortFeature,
0x3,
0x0
};所以上面执行的GetPortStatus函数就是执行的XhcGetRootHubPortStatus函数。在这个函数内部会根据端口的额个数,来决定读取的寄存器的地址,也就是偏移的大小,代码如下:
Offset = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));(XHC_PORTSC_OFFSET=0x0400)
State = XhcReadOpReg (Xhc, Offset);
然后通过上面的函数去读取寄存器的值,XhcReadOpReg函数代码如下
UINT32
XhcReadOpReg (
IN USB_XHCI_INSTANCE *Xhc,
IN UINT32 Offset
)
{
UINT32 Data;
EFI_STATUS Status;
ASSERT (Xhc->CapLength != 0);
Status = Xhc->PciIo->Mem.Read (
Xhc->PciIo,
EfiPciIoWidthUint32,
XHC_BAR_INDEX,
(UINT64) (Xhc->CapLength + Offset),
1,
&Data
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "XhcReadOpReg: Pci Io Read error - %r at %d\n", Status, Offset));
Data = 0xFFFFFFFF;
}
return Data;
}
这里面PciIo其实就是mPciIoInterface这个表,所以PciIo->Mem.Read其实就是函数PciIoMemRead
EFI_PCI_IO_PROTOCOL mPciIoInterface = {
PciIoPollMem,};
PciIoMemRead的代码如下:
EFI_STATUS
EFIAPI
PciIoMemRead (
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
IN UINT8 BarIndex,
IN UINT64 Offset,
IN UINTN Count,
IN OUT VOID *Buffer
)
{
EFI_STATUS Status;
PCI_IO_DEVICE *PciIoDevice;
PciIoDevice = PCI_IO_DEVICE_FROM_PCI_IO_THIS (This);
if ((UINT32)Width >= EfiPciIoWidthMaximum) {
return EFI_INVALID_PARAMETER;
}
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
Status = PciIoVerifyBarAccess (PciIoDevice, BarIndex, PciBarTypeMem, Width, Count, &Offset);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
//
// If request is not aligned, then convert request to EfiPciIoWithXXXUint8
//
if (FeaturePcdGet (PcdUnalignedPciIoEnable)) {
if ((Offset & ((1 << (Width & 0x03)) - 1)) != 0) {
Count *= (UINTN)(1 << (Width & 0x03));
Width = (EFI_PCI_IO_PROTOCOL_WIDTH) (Width & (~0x03));
}
}
Status=PciIoDevice->PciRootBridgeIo->Mem.Read (
PciIoDevice->PciRootBridgeIo,
(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
Offset,
Count,
Buffer
);
if (EFI_ERROR (Status)) {
REPORT_STATUS_CODE_WITH_DEVICE_PATH (
EFI_ERROR_CODE | EFI_ERROR_MINOR,
EFI_IO_BUS_PCI | EFI_IOB_EC_READ_ERROR,
PciIoDevice->DevicePath
);
}
return Status;
}这里PciRootBridgeIo->Mem.Read其实就是函数RootBridgeIoMemRead,其具体的初始化是在PciRootBridgeIo.c中的CreateRootBridge函数中。代码如下(这里只列出了一部分代码):
RootBridge->RootBridgeIo.SegmentNumber = Bridge->Segment;
RootBridge->RootBridgeIo.ParentHandle = HostBridgeHandle;
RootBridge->RootBridgeIo.PollMem = RootBridgeIoPollMem;
RootBridge->RootBridgeIo.PollIo = RootBridgeIoPollIo;
RootBridge->RootBridgeIo.Mem.Read = RootBridgeIoMemRead;
RootBridge->RootBridgeIo.Mem.Write = RootBridgeIoMemWrite;
RootBridge->RootBridgeIo.Io.Read = RootBridgeIoIoRead;
RootBridge->RootBridgeIo.Io.Write = RootBridgeIoIoWrite;
RootBridge->RootBridgeIo.CopyMem = RootBridgeIoCopyMem;
RootBridge->RootBridgeIo.Pci.Read = RootBridgeIoPciRead;
RootBridge->RootBridgeIo.Pci.Write = RootBridgeIoPciWrite;
RootBridge->RootBridgeIo.Map = RootBridgeIoMap;
RootBridge->RootBridgeIo.Unmap = RootBridgeIoUnmap;
RootBridge->RootBridgeIo.AllocateBuffer = RootBridgeIoAllocateBuffer;
RootBridge->RootBridgeIo.FreeBuffer = RootBridgeIoFreeBuffer;
RootBridge->RootBridgeIo.Flush = RootBridgeIoFlush;
RootBridge->RootBridgeIo.GetAttributes = RootBridgeIoGetAttributes;
RootBridge->RootBridgeIo.SetAttributes = RootBridgeIoSetAttributes;
RootBridge->RootBridgeIo.Configuration = RootBridgeIoConfiguration;
进入 RootBridgeIoMemRead函数代码如下:
EFI_STATUS
EFIAPI
RootBridgeIoMemRead (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width,
IN UINT64 Address,
IN UINTN Count,
OUT VOID *Buffer
)
{
EFI_STATUS Status;
UINT64 addr;
if (Address & 0x0800000000000000)
Status = EFI_SUCCESS;
else
Status = RootBridgeIoCheckParameter (This, MemOperation, Width, Address,
Count, Buffer);
if (EFI_ERROR (Status)) {
return Status;
}
if (Address & 0x0800000000000000)
addr = Address;
else
addr = (Address&0xffffffff) | 0x90000e0040000000;
return mCpuIo->Mem.Read (mCpuIo, (EFI_CPU_IO_PROTOCOL_WIDTH) Width, addr, Count, Buffer);
}这里面添加了关于mips访问地址的修改,和官网的源码有一些差异。这个里面又调用了mCpuIo->Mem.Read,这个函数又跳转到了mCpuIo2这个表中的mem中的read函数,这个表位于UefiCpuPkg中的CpuIo2Dxe文件中的CpuIo2Dxe.c中,其代码如下。
EFI_CPU_IO2_PROTOCOL mCpuIo2 = {
{
CpuMemoryServiceRead,
CpuMemoryServiceWrite
},
{
CpuIoServiceRead,
CpuIoServiceWrite
}
};EFI_CPU_IO2_PROTOCOL的定义如下:
struct _EFI_CPU_IO2_PROTOCOL {
///
/// Enables a driver to access memory-mapped registers in the EFI system memory space.
///
EFI_CPU_IO_PROTOCOL_ACCESS Mem;
///
/// Enables a driver to access registers in the EFI CPU I/O space.
///
EFI_CPU_IO_PROTOCOL_ACCESS Io;
};
typedef struct {
} EFI_CPU_IO_PROTOCOL_ACCESS;
因此Mem.Read函数也就是CpuMemoryServiceRead函数,这个函数的代码如下
EFI_STATUS
EFIAPI
CpuMemoryServiceRead (
IN EFI_CPU_IO2_PROTOCOL *This,
IN EFI_CPU_IO_PROTOCOL_WIDTH Width,
IN UINT64 Address,
IN UINTN Count,
OUT VOID *Buffer
)
{
EFI_STATUS Status;
UINT8 InStride;
UINT8 OutStride;
EFI_CPU_IO_PROTOCOL_WIDTH OperationWidth;
UINT8 *Uint8Buffer;
Status = CpuIoCheckParameter (TRUE, Width, Address, Count, Buffer);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Select loop based on the width of the transfer
//
InStride = mInStride[Width];
OutStride = mOutStride[Width];
OperationWidth = (EFI_CPU_IO_PROTOCOL_WIDTH) (Width & 0x03);
for (Uint8Buffer = Buffer; Count > 0; Address += InStride, Uint8Buffer += OutStride, Count--) {
if (OperationWidth == EfiCpuIoWidthUint8) {
*Uint8Buffer = MmioRead8 ((UINTN)Address);
} else if (OperationWidth == EfiCpuIoWidthUint16) {
*((UINT16 *)Uint8Buffer) = MmioRead16 ((UINTN)Address);
} else if (OperationWidth == EfiCpuIoWidthUint32) {
*((UINT32 *)Uint8Buffer) = MmioRead32 ((UINTN)Address);
} else if (OperationWidth == EfiCpuIoWidthUint64) {
*((UINT64 *)Uint8Buffer) = MmioRead64 ((UINTN)Address);
}
}
return EFI_SUCCESS;
}
这个里面调用的MmioRead8/MmioRead16/MmioRead32/MmioRead64在具体的平台上的实现方式不一样,这就是最底层和体系结构相关的读取寄存去的操作函数了,我们在mips里面的实现如下(这里只列出了读取4字节寄存器的函数):
UINT32
EFIAPI
MmioRead32 (
IN UINTN Address
)
{
UINT32 Value;
ASSERT ((Address & 3) == 0);
Value = *(volatile UINT32*)Address;
return Value;
}
到这里就是整个读取一个寄存器的代码实现流程,在uefi中几乎所有的函数调用的过程都是以回调函数的方式实现的,具体函数的接口去哪里找,要看使用的GUID是多少,然后去找这个GUID注册的地方所注册进去的接口函数表是哪一个,在这个表中一定能找到要用的接口函数。
下面我们还回到UsbEnumeratePort函数,获取端口寄存器的状态之后,根据端口的状态做不同的处理。这里面典型的两个状态就是connect和disconnect表示插入优盘,和拔出优盘所对应的状态呢。如果是插入u盘的状态,就执行函数Status = UsbEnumerateNewDev (HubIf, Port); ,拔出U盘执行函数,UsbRemoveDevice (Child);,这两个函数执行完之后,还需要清除一下端口状态变化的状态,执行函数HubApi->ClearPortChange (HubIf, Port);其实就是UsbRootHubClearPortChange,而这个函数又调用的主要就是两个函数一个是获取端口状态寄存器的函数,和上面获取端口状态的流程一样,在一个就是清除端口状态特征的函数,UsbHcClearRootHubPortFeature (HubIf->Device->Bus, Port, (EFI_USB_PORT_FEATURE) Map->Feature);这函数最终调用的还是xhci驱动中的清除端口状态特征的函数。无论是插入了U盘的过程还是拔掉U盘的过程,这个函数都要被执行。
<2>下面看插入U盘的过程:
执行函数UsbEnumerateNewDev (HubIf, Port),首先要进行端口的reset操作,(将端口寄存器的第四bit置位,然后等待硬件完成reset操作)这个reset函数是UsbRootHubResetPort,主要的流程如下:a:执行Status = UsbHcGetRootHubPortStatus (Bus, Port, &PortState);获取端口状态寄存器的状态,若果已经reset完,直接返回。b:执行Status = UsbHcSetRootHubPortFeature (Bus, Port, EfiUsbPortReset);,这个函数的最后一个参数就是reset信号的标志。这个函数里面会根据驱动的安装情况执行xhci中的XhcSetRootHubPortFeature还是ehci中的这个函数。这里执行的xhci驱动中的函数去写寄存器。c:清楚reset信号,寄存器的和这个bit是RW1S属性,也就是写1去设置属性,对这个bit写0不起作用,所以当硬件完成reset操作之后,这个bit会自己设置为0,软件不用去写。等待这个bit归零之后,去设置enable信号。执行函数Status = UsbRootHubSetPortFeature (RootIf, Port, EfiUsbPortEnable); ,然后创建并初始化usb设备,执行函数Child = UsbCreateDevice (HubIf, Port);,知道有一个usb设备插入进来了,然后在获取端口寄存器的状态,判断是什么速度的设备,一次来区分是2.0的U盘还是3.0的U盘。设置玩速度之后,执行函数Status = UsbGetMaxPacketSize0 (Child);来获取最大的包个数,3.0是512,2.0是64,然后执行函数 Status = UsbBuildDescTable (Child);来构造获取设备的描述符表(里面主要包含三个函数,获取描述符的函数,解析描述符的函数,然后是构造language ID table的函数)。然后开始执行 Status = UsbSetConfig (Child, Config);函数,开始进行设备的配置设置。这些函数都是在USBBUS层实现的,但是具体的配置函数如何去配置,这都是在xhci驱动中实现的。具体的调用关系请看代码。配置好之后,如果没有错误,就退出这个函数,然后进入到清楚端口状态变化的函数HubApi->ClearPortChange (HubIf, Port);其实就是UsbRootHubClearPortChange来清楚端口寄存器的变化。
这样整个插入的过程就完成了,具体的初始化设备函数就是在UsbEnumerateNewDev函数中完成的。
<3>下面看一下U盘拔出的过程:
还是在定时器的事件中,隔一段时间执行函数UsbRootHubEnumeration,在这个函数内主要是UsbEnumeratePort (RootHub, Index);这个函数,一次遍历每一个端口,检测到端口的寄存器发生变化,就会执行下面的函数
// Following as the above cases, it's safety to remove and create again.
//
Child = UsbFindChild (HubIf, Port);
if (Child != NULL) {
DEBUG (( EFI_D_INFO, "UsbEnumeratePort: device at port %d removed from root hub %p\n", Port, HubIf));
UsbRemoveDevice (Child);
主要的移除操作都是在函数UsbRemoveDevice中完成的,代码如下
EFI_STATUS
UsbRemoveDevice (
IN USB_DEVICE *Device
)
{
USB_BUS *Bus;
USB_DEVICE *Child;
EFI_STATUS Status;
EFI_STATUS ReturnStatus;
UINTN Index;
Bus = Device->Bus;
//
// Remove all the devices on its downstream ports. Search from devices[1].
// Devices[0] is the root hub.
//
ReturnStatus = EFI_SUCCESS;
for (Index = 1; Index < Bus->MaxDevices; Index++) {
Child = Bus->Devices[Index];
if ((Child == NULL) || (Child->ParentAddr != Device->Address)) {
continue;
}
Status = UsbRemoveDevice (Child);
if (!EFI_ERROR (Status)) {
Bus->Devices[Index] = NULL;
} else {
Bus->Devices[Index]->DisconnectFail = TRUE;
ReturnStatus = Status;
DEBUG ((EFI_D_INFO, "UsbRemoveDevice: failed to remove child %p at parent %p\n", Child, Device));
}
}
if (EFI_ERROR (ReturnStatus)) {
return ReturnStatus;
}if (EFI_ERROR (ReturnStatus)) {
return ReturnStatus;
}
Status = UsbRemoveConfig (Device);
if (!EFI_ERROR (Status)) {
DEBUG (( EFI_D_INFO, "UsbRemoveDevice: device %d removed\n", Device->Address));
ASSERT (Device->Address < Bus->MaxDevices);
Bus->Devices[Device->Address] = NULL;
UsbFreeDevice (Device);
} else {
Bus->Devices[Device->Address]->DisconnectFail = TRUE;
}
return Status;
}
按照上面的代码,首先遍历所有设备列表,看看那个设备移除了,这是个迭代函数,自己会调自己,入参就是设备节点。一次编列每一个节点设备。当这个节点的设备移除之后,条件满足执行下面的移除配置函数Status = UsbRemoveConfig (Device);函数代码如下:(具体实现请看代码,这里不做想写的介绍)
EFI_STATUS
UsbRemoveConfig (
IN USB_DEVICE *Device
)
{
USB_INTERFACE *UsbIf;
UINTN Index;
EFI_STATUS Status;
EFI_STATUS ReturnStatus;
//
// Remove each interface of the device
//
ReturnStatus = EFI_SUCCESS;
for (Index = 0; Index < Device->NumOfInterface; Index++) {
ASSERT (Index < USB_MAX_INTERFACE);
UsbIf = Device->Interfaces[Index];
if (UsbIf == NULL) {
continue;
}
Status = UsbDisconnectDriver (UsbIf);
if (!EFI_ERROR (Status)) {
UsbFreeInterface (UsbIf);
Device->Interfaces[Index] = NULL;
} else {
ReturnStatus = Status;
}
}
Device->ActiveConfig = NULL;
return ReturnStatus;
}