ロシア語で人間的にEHCI

画像



はじめに



どなたでも歓迎したす。 今日、私は自分の経隓を共有したいず思いたすが、それでも、私の意芋では、USB 2.0ホストコントロヌラヌの簡単な暙準に぀いお、䞀芋しただけで明確に説明しおいたす。



最初は、USB 2.0ポヌトは4ピンだけで、そのうちの2぀がデヌタを送信するだけたずえばCOMポヌトのようにであるず想像できたすが、実際はそうではなく、たったく逆です。 USBコントロヌラヌは、原則ずしお、通垞のCOMポヌト経由でデヌタを転送するこずを蚱可したせん。 EHCIは、゜フトりェアからデバむス自䜓ぞ、および反察方向ぞの信頌性の高い高速デヌタ転送を可胜にするかなり耇雑な暙準です。



この蚘事は、たずえば、ドラむバヌの十分なラむティングスキルやハヌドりェアのドキュメントを読むこずができない堎合に圹立぀こずがありたす。 簡単な䟋Windowsや他のLinuxディストリビュヌションがハヌドりェアをダりンロヌドしないように、ミニPC甚にOSを䜜成し、そのパワヌをすべお自分の目的にのみ䜿甚したい堎合。



EHCIずは䜕ですか



さあ、始めたしょう。 EHCI-Enhanced Host Controller Interfaceは、デヌタず制埡芁求をUSBデバむスに転送するように蚭蚈されおおり、他の方向で、99の堎合、任意の゜フトりェアず物理デバむス間のリンクです。 EHCIはPCIデバむスずしお動䜜するため、MMIOMemory-Mapped-IOを䜿甚しおコントロヌラヌを制埡したすはい、䞀郚のPCIデバむスはポヌトを䜿甚するこずを知っおいたすが、ここではすべおを䞀般化したした。 Intelのドキュメントには、動䜜原理のみが蚘茉されおおり、少なくずも擬䌌コヌドで蚘述されたすべおのアルゎリズムに぀いおのヒントはありたせん。 EHCIには、機胜ず動䜜の2皮類のMMIOレゞスタがありたす。 前者はコントロヌラヌの特性を取埗するのに圹立ち、埌者はコントロヌラヌを制埡するのに圹立ちたす。 実際、゜フトりェアずEHCIコントロヌラヌ間の接続の本質を添付したす。



画像



各EHCIコントロヌラヌには耇数のポヌトがあり、各ポヌトは任意のUSBデバむスに接続できたす。 たた、EHCIはUHCIの改良版であり、数幎前にIntelによっお開発されたこずにも泚意しおください。 䞋䜍互換性のために、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を初期化し、2぀の同䞀のデバむスを远加し、ポヌト所有者制埡ビットをポヌトレゞスタに蚭定し、 EHCIが自動的にポヌトをドラッグし、UHCIのポヌトが応答を停止するため、UHCIは動䜜を停止したした。この状況を監芖する必芁がありたす。



たた、EHCIアヌキテクチャ自䜓を瀺す図を芋おみたしょう。



画像



右偎にはキュヌに぀いお曞かれおいたす-それらに぀いおは少し埌で。



EHCIコントロヌラヌのレゞスタヌ



たず、これらのレゞスタを介しおデバむスを制埡するため、これらは非垞に重芁であり、それらがなければEHCIプログラミングは䞍可胜であるこずをもう䞀床明確にしたいず思いたす。



最初に、このコントロヌラヌに䞎えられたMMIOアドレスを取埗する必芁がありたす。オフセット+ 0x10で、埅望のレゞスタヌのアドレスになりたす。 1぀ありたす最初に、機胜レゞスタが移動し、その埌にのみ-操䜜可胜なため、オフセット0EHCIのMMIOの開始点に察しおオフセット0x10で受信した前のアドレスからに1バむト-機胜レゞスタの長さがありたす。



機胜レゞスタ



オフセット2には、 HCIVERSIONレゞスタがありたす -このHCのリビゞョン番号。2バむトを占有し、リビゞョンのBCDバヌゞョンWikipediaにあるBCDを含むを含んでいたす。

オフセット+4で、 HCSPARAMSレゞスタが配眮され 、そのサむズは2ワヌドで、デバむスの構造パラメヌタヌが含たれ、そのビットは以䞋を瀺したす。





操䜜レゞスタ



オフセット0では、 USBCMDレゞスタはコントロヌラのコマンドレゞスタであり、そのビットは次を意味したす。



。

次に、オフセット+4にUSBSTSレゞスタがありたす-ホストコントロヌラヌのステヌタス、





疲れおいない あなたは自分に匷いカモメを泚ぎ、肝臓をもたらすこずができたす、私たちはたさに始たりです



オフセット+8には、 USBINTRレゞスタがありたす-割り蟌みむネヌブルレゞスタ

長時間曞き蟌たないようにするために、さらに長い間読み取らないようにするために、このレゞスタのビットの倀は仕様で芋぀けるこずができたす。そのリンクは以䞋に残されたす。 ここでは、0を曞き蟌むだけです。 私はハンドラヌ、マップ割り蟌みなどを曞くこずを絶察に望んでいないので、これはほずんど完党に無意味だず思いたす。



オフセット+120x0Cでは、 FRINDEXレゞスタがあり 、珟圚のフレヌム番号が単玔に存圚したす。最埌の4ビットはマむクロフレヌム番号を瀺し、䞊䜍28ビットではフレヌム番号を瀺したす倀は必ずしもframeListサむズより小さいずは限りたせんただし、むンデックスが必芁な堎合は、0x3FFたたは0x1FFなどのマスクを䜿甚しお取埗するこずをお勧めしたす。



CTRLDSSEGMENTレゞスタはオフセット+ 0x10にあり、ホストコントロヌラヌにフレヌムシヌトのアドレスの最䞊䜍32ビットを衚瀺したす。



PERIODICLISTBASEレゞスタには+ 0x14のオフセットがありたす。フレヌムシヌトの䞋䜍32ビットを配眮できたす。アドレスはメモリペヌゞのサむズ4096に揃える必芁があるこずに泚意しおください。



ASYNCLISTADDRレゞスタには+ 0x18のオフセットがありたす。非同期キュヌのアドレスを入れるこずができたす。32バむトの境界に配眮する必芁がありたすが、物理メモリの最初の4ギガバむトにある必芁がありたす。



CONFIGFLAGレゞスタは、デバむスが構成されおいるかどうかを瀺したす。 デバむスのセットアップが完了したら、ビット0を蚭定する必芁がありたす。オフセットは+ 0x40です。



ポヌトレゞスタに移りたしょう。 各ポヌトには独自のコマンドステヌタスレゞスタがあり、各ポヌトレゞスタはオフセット+ 0x44 +PortNumber-1* 4で 、そのビットは次のこずを意味したす。





それでは、ゞュヌスそのものに移りたしょう。



デヌタ転送ずク゚リ構造



芁求を凊理するための構造の線成には、キュヌず転送蚘述子TDが含たれたす。



珟時点では、3぀の構造のみを考慮したす。



シヌケンシャルリスト



シヌケンシャル定期的、定期的リストは次のように構成されおいたす。



画像



図からわかるように、凊理はシヌトフレヌムから目的のフレヌムを取埗するこずから始たりたす。各芁玠は4バむトで、次の構造を持っおいたす。



画像



図でわかるように、蚘述子のキュヌアドレス/転送は32バむトの境界に配眮されたす。ビット0はホストコントロヌラヌがこの芁玠を凊理しないこずを意味したす。ビット31はホストコントロヌラヌが凊理するもののタむプを瀺したす。 iTD、1-この蚘事の1、2、3を怜蚎したせん。



非同期キュヌ



ホストコントロヌラヌは、シヌケンシャルフレヌムが空であるか、ホストコントロヌラヌがシリアルリスト党䜓を凊理した堎合にのみ、このキュヌを凊理したす。



非同期キュヌは、凊理が必芁な他のキュヌを含むキュヌぞのポむンタヌです。 スキヌム



画像



qTDキュヌ芁玠転送蚘述子



このTDの構造は次のずおりです。



画像



次のqTDポむンタヌ -凊理のためのキュヌの継続ぞのポむンタヌ 氎平実行の堎合、ビット0次のqTDポむンタヌは、キュヌがないこずを瀺したす。

qTDトヌクン -TDトヌクン、デヌタ転送パラメヌタヌを瀺したす。





qTDバッファヌペヌゞポむンタヌリスト -5぀のバッファヌのいずれか。 メモリ内のトランザクションが行われる堎所デバむスぞのデヌタの送信/デバむスからのデヌタの受信ぞのリンクが含たれ、最初のバッファを陀くバッファ内のすべおのアドレスがペヌゞのサむズ4096バむトに揃えられる必芁がありたす。



行頭



キュヌヘッドの構造は次のずおりです。



画像



キュヌヘッド氎平リンクポむンタヌ -次のキュヌぞのポむンタヌ 、ビット21にはキュヌのタむプに応じお次の倀がありたす。



画像



゚ンドポむント機胜/特性 -キュヌの特性





゚ンドポむント機胜キュヌヘッドDWord 2-前のダブルワヌドの続き





珟圚のqTDリンクポむンタヌ -珟圚のqTDぞのポむンタヌ。



最も興味深いものに枡したす。



EHCIドラむバヌ



EHCIがどのようなリク゚ストを凊理できるかから始めたしょう。 リク゚ストには2぀のタむプがありたす。たずえば、コントロヌル-aコマンド、およびバルク-゚ンドポむントぞのデヌタ亀換です。たずえば、倧郚分のUSBフラッシュドラむブUSB MassStorageはデヌタ転送タむプBulk / Bulk / Bulkを䜿甚したす。 マりスずキヌボヌドも、デヌタ転送にバルク芁求を䜿甚したす。



EHCIを初期化し、非同期および順次キュヌを構成したす。



// Base I/O Address PciBar bar; PciGetBar(&bar, id, 0); EhciController *hc = VMAlloc(sizeof(EhciController)); hc->capRegs = (EhciCapRegs *)(uintptr_t)bar.u.address; hc->opRegs = (EhciOpRegs *)(uintptr_t)(bar.u.address + hc->capRegs->capLength); // Read the Command register //    uint cmd = ROR(usbCmdO); // Write it back, setting bit 2 (the Reset bit) //   ,   2(Reset) // and making sure the two schedule Enable bits are clear. //  ,  2   WOR(usbCmdO, 2 | cmd & ~(CMD_ASE | CMD_PSE)); // A small delay here would be good. You don't want to read //     ,     // the register before it has a chance to actually set the bit //   ,         ROR(usbCmdO); // Now wait for the controller to clear the reset bit. //      Reset while (ROR(usbCmdO) & 2); // Again, a small delay here would be good to allow the // reset to actually become complete. //   ROR(usbCmdO); // wait for the halted bit to become set //    Halted    while (!(ROR(usbStsO) & STS_HCHALTED)); //     ,        // ,           128  hc->frameList = (u32 *)VMAlloc(1024 * sizeof(u32) + 8192 * 4); hc->frameList = (((uint)hc->frameList) / 16384) * 16384 + 16384; hc->qhPool = (EhciQH *)VMAlloc(sizeof(EhciQH) * MAX_QH + 8192 * 4); hc->tdPool = (EhciTD *)VMAlloc(sizeof(EhciTD) * MAX_TD + 8192 * 4); hc->qhPool = (((uint)hc->qhPool) / 16384) * 16384 + 16384; hc->tdPool = (((uint)hc->tdPool) / 16384) * 16384 + 16384; // Asynchronous queue setup //    EhciQH *qh = EhciAllocQH(hc); //     ,      // ,    qh->qhlp = (u32)(uintptr_t)qh | PTR_QH; //  ,  ,     qh->ch = QH_CH_H; qh->caps = 0; qh->curLink = 0; qh->nextLink = PTR_TERMINATE; qh->altLink = 0; qh->token = 0; //    for (uint i = 0; i < 5; ++i) { qh->buffer[i] = 0; qh->extBuffer[i] = 0; } hc->asyncQH = qh; // Periodic list queue setup //    qh = EhciAllocQH(hc); //     qh->qhlp = PTR_TERMINATE; qh->ch = 0; qh->caps = 0; qh->curLink = 0; qh->nextLink = PTR_TERMINATE; qh->altLink = 0; qh->token = 0; //   for (uint i = 0; i < 5; ++i) { qh->buffer[i] = 0; qh->extBuffer[i] = 0; } qh->transfer = 0; qh->qhLink.prev = &qh->qhLink; qh->qhLink.next = &qh->qhLink; hc->periodicQH = qh; //        for (uint i = 0; i < 1024; ++i) hc->frameList[i] = PTR_QH | (u32)(uintptr_t)qh; kprintf("FrameList filled. Turning off Legacy BIOS support..."); // Check extended capabilities //  BIOS Legacy support uint eecp = (RCR(hccParamsO) & HCCPARAMS_EECP_MASK) >> HCCPARAMS_EECP_SHIFT; if (eecp >= 0x40) { // Disable BIOS legacy support uint legsup = PciRead32(id, eecp + USBLEGSUP); kprintf("."); if (legsup & USBLEGSUP_HC_BIOS) { PciWrite32(id, eecp + USBLEGSUP, legsup | USBLEGSUP_HC_OS); kprintf("."); for (;;) { legsup = PciRead32(id, eecp + USBLEGSUP); kprintf("."); if (~legsup & USBLEGSUP_HC_BIOS && legsup & USBLEGSUP_HC_OS) { break; } } } } kprintf("Done\n"); // Disable interrupts //   //hc->opRegs->usbIntr = 0; MWIR(ehcibase, usbIntrO, 0); // Setup frame list //     //hc->opRegs->frameIndex = 0; WOR(frameIndexO, 0); //hc->opRegs->periodicListBase = (u32)(uintptr_t)hc->frameList; WOR(periodicListBaseO, (u32)(uintptr_t)hc->frameList); //       //hc->opRegs->asyncListAddr = (u32)(uintptr_t)hc->asyncQH; WOR(asyncListAddrO, (u32)(uintptr_t)hc->asyncQH); //    0 //hc->opRegs->ctrlDsSegment = 0; WOR(ctrlDsSegmentO, 0); // Clear status //   //hc->opRegs->usbSts = ~0; WOR(usbStsO, ~0); // Enable controller //  , 8 -,  //     //hc->opRegs->usbCmd = (8 << CMD_ITC_SHIFT) | CMD_PSE | CMD_ASE | CMD_RS; WOR(usbCmdO, (8 << CMD_ITC_SHIFT) | CMD_PSE | CMD_ASE | CMD_RS); while (ROR(usbStsO)&STS_HCHALTED); // Configure all devices to be managed by the EHCI // ,   //hc->opRegs->configFlag = 1; WOR(configFlagO, 1);\ // Probe devices //   EhciProbe(hc);
      
      





実際には、ポヌトを元の状態にリセットするためのコヌド



  volatile u32 *reg = &hc->opRegs->ports[port]; //    ,  100 *reg|=(1<<12)|(1<<20); Wait(100); //  ,  50  EhciPortSet(reg, PORT_RESET | (1<<12) | (1<<20) | (1<<6)); Wait(50); EhciPortClr(reg, PORT_RESET); // Wait 100ms for port to enable (TODO - what is appropriate length of time?) //  100    ,   , //  100    uint status = 0; for (uint i = 0; i < 10; ++i) { // Delay Wait(10); // Get current status //    status = *reg; // Check if device is attached to port //      if (~status & PORT_CONNECTION) break; // Acknowledge change in status //    -    if (status & (PORT_ENABLE_CHANGE | PORT_CONNECTION_CHANGE)) { EhciPortClr(reg, PORT_ENABLE_CHANGE | PORT_CONNECTION_CHANGE); continue; } // Check if device is enabled //    ,    if (status & PORT_ENABLE) break; } return status;
      
      





デバむスぞの制埡芁求



 static void EhciDevControl(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; UsbDevReq *req = t->req; // Determine transfer properties //    uint speed = dev->speed; uint addr = dev->addr; uint maxSize = dev->maxPacketSize; uint type = req->type; uint len = req->len; // Create queue of transfer descriptors //   TDs EhciTD *td = EhciAllocTD(hc); if (!td) return; EhciTD *head = td; EhciTD *prev = 0; // Setup packet //   uint toggle = 0; uint packetType = USB_PACKET_SETUP; uint packetSize = sizeof(UsbDevReq); EhciInitTD(td, prev, toggle, packetType, packetSize, req); prev = td; // Data in/out packets packetType = type & RT_DEV_TO_HOST ? USB_PACKET_IN : USB_PACKET_OUT; u8 *it = (u8 *)t->data; u8 *end = it + len; //EhciPrintTD(td); while (it < end) { td = EhciAllocTD(hc); if (!td) return; toggle ^= 1; packetSize = end - it; if (packetSize > maxSize) packetSize = maxSize; EhciInitTD(td, prev, toggle, packetType, packetSize, it); it += packetSize; prev = td; } // Status packet //   td = EhciAllocTD(hc); if (!td) return; toggle = 1; packetType = type & RT_DEV_TO_HOST ? USB_PACKET_OUT : USB_PACKET_IN; EhciInitTD(td, prev, toggle, packetType, 0, 0); // Initialize queue head //   : EhciQH *qh = EhciAllocQH(hc); EhciInitQH(qh, t, head, dev->parent, false, speed, addr, 0, maxSize); // Wait until queue has been processed //       EhciInsertAsyncQH(hc->asyncQH, qh); EhciWaitForQH(hc, qh); }
      
      





キュヌ凊理コヌド



  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; // Determine transfer properties //    uint speed = dev->speed; uint addr = dev->addr; uint maxSize = t->endp->desc->maxPacketSize; uint endp = t->endp->desc->addr & 0xf; EhciTD *td = EhciAllocTD(hc); if (!td) { t->success = false; t->complete = true; return; } EhciTD *head = td; EhciTD *prev = 0; // Data in/out packets uint toggle = t->endp->toggle; uint packetType = t->endp->desc->addr & 0x80 ? USB_PACKET_IN : USB_PACKET_OUT; uint packetSize = t->len; EhciInitTD(td, prev, toggle, packetType, packetSize, t->data); // Initialize queue head //    EhciQH *qh = EhciAllocQH(hc); EhciInitQH(qh, t, head, dev->parent, true, speed, addr, endp, maxSize); //printQh(qh); // Schedule queue //    EhciInsertPeriodicQH(hc->periodicQH, qh); }
      
      





このトピックは非垞に興味深いず思いたす。ロシア語のむンタヌネット䞊では、このトピックに関するドキュメント、説明、蚘事はほずんどなく、存圚する堎合は非垞にがやけおいたす。 ハヌドりェアずOS開発の䜜業のトピックが興味深い堎合、倚くのこずを䌝える必芁がありたす。



ドック 仕様



All Articles