
引言
我欢迎大家 今天,我想分享我的经验,但我认为,乍一看,我仍然清楚地解释了USB 2.0主机控制器的简单标准。
最初,您可以想象一个USB 2.0端口只有4个引脚,其中两个仅用于传输数据(例如,一个COM端口),但实际上并非如此,甚至完全相反。 原则上,USB控制器不允许我们像通过常规COM端口那样传输数据。 EHCI是一个相当复杂的标准,它允许以相反的方向从软件到设备本身进行可靠,快速的数据传输。
例如,如果您没有足够的驱动程序编写技能和硬件的阅读文档,您可能会发现本文很有用。 一个简单的例子:您要为小型PC编写OS,以使某些Windows或其他Linux发行版本不下载硬件,并且将其所有功能专用于您自己的目的。
什么是EHCI?
好吧,让我们开始吧。 EHCI-增强型主机控制器接口,旨在将数据和控制请求传输到USB设备,并且在另一个方向上进行传输,在99%的情况下,它是任何软件和物理设备之间的链接。 EHCI用作PCI设备,因此使用MMIO(内存映射的IO)来控制控制器(是的,我知道有些PCI设备使用端口,但是这里我概括了所有内容)。 英特尔的文档仅描述了操作原理,而对于至少以伪代码编写的所有算法都没有任何提示。 EHCI有两种类型的MMIO寄存器:功能和操作。 前者用来获得控制器的特性,而后者用来控制它。 实际上,我将附上软件与EHCI控制器之间连接的本质:

每个EHCI控制器都有几个端口,每个端口都可以连接到任何USB设备。 另外,请注意,EHCI是UHCI的改进版本,它也是英特尔几年前开发的。 为了向后兼容,任何版本低于EHCI的UHCI / OHCI控制器都将作为EHCI的伴侣。 例如,您有一个可以在USB 1.1上使用的USB键盘(到目前为止,到目前为止,大多数键盘都是这样的)(请注意,USB 1.1的最大速度为每秒12兆位,而FullSpeed USB 2.0具有带宽最高可达480 Mbps),并且您有一台带有USB 2.0端口的计算机,将键盘连接到计算机时,EHCI主机控制器可以与USB 1.1一起使用。 下图显示了该模型:

另外,将来,我想立即警告您,由于这种荒唐的情况,您的驱动程序可能无法正常工作:您先初始化UHCI,然后初始化EHCI,同时添加两个相同的设备,将端口所有者控制位设置为端口寄存器,然后由于EHCI自动将端口拖到自身上,并且UHCI上的端口停止响应,因此UHCI停止工作,需要对此情况进行监视。
另外,让我们看一下显示EHCI体系结构本身的图:

右边写的是关于队列的信息,稍后再写。
EHCI控制器寄存器
首先,我想再次澄清一下,通过这些寄存器,您将控制您的设备,因此它们非常重要-没有它们,EHCI编程是不可能的。
首先,您需要获取提供给该控制器的MMIO地址,偏移量+ 0x10将是我们等待已久的寄存器的地址。 有一件事情:首先,能力寄存器进入,并且只有在它们之后-操作,因此,偏移量为0(相对于EHCI MMIO的起始地址在偏移量0x10处接收到的先前地址),只有一个字节—能力寄存器的长度。
能力寄存器
HCIVERSION寄存器
位于偏移量2
处 -该HC的修订版号,占2个字节,并包含修订版的BCD版本(可以在Wikipedia上找到什么BCD)。
HCSPARAMS寄存器
位于偏移量+4
处 ,其大小为2个字,它包含器件的结构参数,其位如下所示:
- 位16-端口指示器-已连接的USB设备的可用LED。
- 位15:12-分配给该控制器的配套控制器的编号
- 位11:8-随播控制器上的端口数
- 位7-端口路由规则-显示这些端口如何映射到配套端口
- 位4-端口电源控制-指示是否需要打开每个端口的电源,0-自动提供电源
- 位3:0-此控制器的端口数。
- HCCPARAMS寄存器位于偏移量+8处-它显示兼容性参数,其位表示以下内容:
- 位2-异步队列可用性,
- 位1-定期(顺序)队列可用性
- 位0-64位兼容性
操作寄存器
在偏移0处,
USBCMD寄存器是控制器的命令寄存器,其位表示以下含义:
- 位23:16-中断阈值控制-显示一个常规帧将使用多少个微帧。 越大,速度越快,但如果大于8,则微帧将以与8相同的速度处理。
- 位6-异步队列中的每个事务之后中断,
- 位5-是使用的异步队列
- 第4位-顺序队列的使用,
- 位3:2-FrameList'a的大小(稍后会详细介绍)。 0表示1024个元素,1-512,2-256,3-保留
- 位1 —设置为重置主机控制器。
- 位0-运行/停止
。
接下来,在偏移量+4处,有一个
USBSTS寄存器-主机控制器的状态,
- 位15指示是否正在使用异步队列。
- 位14指示是否正在使用顺序队列,
- 位13-指示已检测到空异步队列,
- 位12设置为1,如果在处理事务时发生错误,则主机控制器将停止所有队列。
- 位4设置为1,如果发生严重错误,主机控制器将停止所有队列。
- Bit 3 FrameList(寄存器)翻转-当主机控制器处理整个frameList时,设置为1。
- 位1-USB错误中断-是否生成错误中断?
- 位0-USB中断-如果在TD中安装了IOC,则在成功进行事务处理后置位
不累吗 您可以给自己倒一个坚固的海鸥并带上肝脏,我们才刚刚起步!
在偏移+8处,有一个
USBINTR寄存器-中断使能寄存器
为了长时间不写入,甚至更长时间不写入,可以在规范中找到该寄存器的位值,其链接将留在下面。 在这里我只写0,因为 我绝对不希望编写处理程序,映射中断等,因此我认为这几乎是毫无意义的。
在偏移量+12(0x0C)处,位于
FRINDEX寄存器中,当前帧号仅位于其中,我要注意的是,最后4位显示微帧号,在高28位中显示帧号(该值不一定小于frameList的大小)但是,如果您需要索引,最好带0x3FF(或0x1FF等)的掩码。
CTRLDSSEGMENT寄存器的偏移量为+ 0x10;它向主机控制器显示了帧表地址的最高32位。
PERIODICLISTBASE寄存器的偏移量为+ 0x14,可以将帧表的低32位放入其中,请注意,该地址应与内存页的大小(4096)对齐。
ASYNCLISTADDR寄存器的偏移量为+ 0x18,您可以将异步队列的地址放入其中,请注意,它必须在32字节的边界处对齐,而它必须在物理内存的前4 GB中。
CONFIGFLAG寄存器指示是否配置了器件。 完成设备设置后,必须将位0设置为0,偏移量为+ 0x40。
让我们继续进行端口寄存器。 每个端口都有自己的命令状态寄存器,每个端口寄存器的偏移量为
+ 0x44 +(PortNumber-1)* 4 ,其位表示以下含义:
- 位12-端口电源,1-提供电源,0-否。
- 位8-端口静止-设置为复位设备。
- 位3-端口启用/禁用更改-更改端口的“包含”状态时设置。
- 位2-端口打开/关闭。
- 位1-更改连接状态,例如,如果您连接或断开了USB设备,则设置为1。
- 位0-连接状态,1-已连接,0-否。
现在让我们继续汁液本身。
数据传输和查询结构
组织用于处理请求的结构包括队列和传输描述符(TD)。
目前,我们仅考虑3种结构。
顺序表
顺序(周期性,周期性)列表的组织方式如下:

如您在图中所看到的,处理开始于从工作表框架中获取所需的框架,其每个元素占用4个字节,并具有以下结构:

如图所示,队列地址/描述符传输在32个字节的边界对齐,位0表示主机控制器将不处理此元素,位3:1表示主机控制器将处理的类型:0-等时TD (iTD),第1转,第2和第3条,我将不予考虑。
异步队列
仅当顺序帧为空或主机控制器已处理整个序列表时,主机控制器才处理此队列。
异步队列是指向包含其他需要处理的队列的队列的指针。 方案:

qTD(队列元素传输描述符)
该TD具有以下结构:
下一个qTD指针 -指向要继续处理的队列的指针(用于水平执行),位0下一个qTD指针指示没有其他队列。
qTD令牌 -TD令牌,显示数据传输参数:
- 第31位-数据切换(稍后会详细介绍)
- 位30:16-要传输的数据量,在事务完成之后,其值将减少传输的数据量。
- 位15-IOC-中断完成-在描述符处理完成后引起中断。
- 位14:12显示了当前缓冲区的交换编号,从该缓冲区交换数据,稍后将对此进行更多说明。
- 位11:10-允许的错误数。 下表显示错误计数减少的时间:

脚注1-检测到Babble或Stall会自动停止队列开头的执行。 脚注3-主机存在数据缓冲区错误。 他们不考虑设备重试。 - 9:8-PID代码-令牌类型:0-输入令牌(从主机到设备),1-输出令牌(从设备到主机),2-“ SETUP”令牌
- 位7:0指示TD状态:
位7表示TD处于活动状态(即,主机控制器处理该TD)
位6-暂停-表示已发生错误,并且TD执行已停止。
位4-已检测到Babble-我们发送到设备的数据量(或每转一圈)少于我们传输的数据量,例如,该设备向我们发送了100字节的数据,而我们仅读取了50个字节,然后又读取了50个字节如果该位设置为1,还将设置Halted位。
位3-交易错误-交易期间发生错误。
qTD缓冲区页面指针列表 -5个缓冲区中的任何一个。 它包含一个链接,指向要在内存中进行交易的位置(将数据发送到设备/从设备接收数据),缓冲区中除第一个缓冲区外的所有地址都应与页面的大小(4096字节)对齐。
线头
队列头具有以下结构:
队列头水平链接指针 -指向下一个队列的指针,位2:1根据队列类型具有以下值:
端点功能/特征 -队列特征:
- 位26:16包含用于传输的最大数据包大小
- 位14:数据切换控制-显示主机控制器应在何处获取初始数据切换值,0-忽略qTD中的DT位,将DT位保存为队列头。
- 位13:12-传输速率特征:

- 位11:8-向其发出请求的端点的编号
- 位6:0-设备地址
端点功能:队列头DWord 2-上一个双字的延续:
当前qTD链接指针 -指向
当前qTD的指针 。
我们传递给最有趣的。
EHCI驱动程序
让我们从EHCI可以满足的查询开始。 有两种类型的请求:控制-一个la命令,以及批量-到端点,用于数据交换,例如,绝大多数USB闪存驱动器(USB MassStorage)使用数据传输类型Bulk / Bulk / Bulk。 鼠标和键盘还使用批量请求进行数据传输。
初始化EHCI并配置异步和顺序队列:
实际上,用于将端口重置为其原始状态的代码:
volatile u32 *reg = &hc->opRegs->ports[port];
对设备的控制请求:
static void EhciDevControl(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; UsbDevReq *req = t->req;
队列处理代码:
if (qh->token & TD_TOK_HALTED) { t->success = false; t->complete = true; } else if (qh->nextLink & PTR_TERMINATE) if (~qh->token & TD_TOK_ACTIVE) { if (qh->token & TD_TOK_DATABUFFER) kprintf(" Data Buffer Error\n"); if (qh->token & TD_TOK_BABBLE) kprintf(" Babble Detected\n"); if (qh->token & TD_TOK_XACT) kprintf(" Transaction Error\n"); if (qh->token & TD_TOK_MMF) kprintf(" Missed Micro-Frame\n"); t->success = true; t->complete = true; } if (t->complete) ....
现在是端点请求(批量请求)
static void EhciDevIntr(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc;
我认为该主题非常有趣,俄语版本的互联网上几乎没有有关该主题的文档,描述和文章,如果有的话,则非常模糊。 如果涉及硬件和OS开发的主题很有趣,那么有很多事情要说。
底座:
规格