📜 ⬆️ ⬇️

UHCI, or the very first USB



Good day, dear reader! I was asked to write about UHCI - well, I write.

Perhaps this article is useful to you if, for example, you do not have sufficient skills in writing drivers and reading the documentation for the hardware. A simple example: if you want to write your own OS for a mini-PC, so that some kind of Windows or another Linux distribution will not load hardware, and you will use all its power solely for your own purposes.

What is UHCI?


I think that once again not to be sprayed on what and why, just leave a link to my previous article on EHCI. Tyk here
UHCI - Universal Host Controller Interface, works as a PCI device, but, unlike EHCI, it uses ports instead of MMIO (Memory-Mapped-IO).
')


Terms to be used further



Types of data transfer


Isochronous — an isosynchronous transfer that has a specified data transfer rate. It can be used, for example, for USB microphones, etc.

Interrupt - Small, spontaneous data transfers from the device. Interrupt transfer type supports devices that require a predictable service interval, but do not necessarily provide a predictable data flow. Commonly used for devices such as keyboards and pointing devices that may not produce data for long periods of time, but require a quick response when they have data to send.

Control - The type of transmission of information about the device status, state and configuration. The Control type is used to provide a control channel from Host to USB devices. Control transfers always consist of a setup phase and zero or more data phases, followed by a state phase. It is imperative that the transfer of control to the specified endpoint is processed in FIFO mode. If control is passed to the same endpoint, the rotation can lead to unpredictable behavior.

Bulk - the type of data transfer. Used, for example, in MassStorage devices.



Here is the distribution of 1ms of time - processing of one frame.

Time distribution


The host controller supports real-time data delivery, generating a Start Of Frame (SOF) packet every 1 ms. A SOF packet is generated when the SOF counter in the host controller expires (Figure 3). The host controller initializes the SOF counter for a 1 ms frame time. Small changes can be made to this value (and, therefore, the frame time period) by programming the register of changes in the SOF. This feature allows minor changes to be made during the frame time period, if necessary, to maintain real-time synchronization across the entire USB system.

The host controller includes the frame number in each SOF packet. This frame number uniquely identifies the frame period in real time. The frame termination condition (EOF) occurs at the end of the time interval of 1 ms, when the host controller starts the next frame time, generating another SOF packet with the corresponding frame number. During a frame period, data is transmitted in the form of packets of information. The frame time period is strictly enforced by the host controller, and data packets in the current frame cannot exceed the EOF limits (see Chapter 11 in the USB specification). The host controller supports synchronization of data transmission between frames in real time, associating the frame number with the execution of a specific entry in the frame list. The host controller's frame counter generates a frame number (11-bit value) and includes it in each SOF packet. The counter is programmed through registers and increases each frame period. The host controller uses the lower 10 bits of the frame number as an index in the list of frames with 1024 frames, which is stored in system memory. Thus, since the frame counter controls the selection of a record from the frame list, the host controller processes each entry in the list in a given frame period. The host controller is incremented to the next entry in the frame list for each new frame. This ensures that isochronous transfers are performed in a specific frame.

Figure 3:



UHCI structure


It's all exactly the same as with EHCI. Example of requests to HC:



Setup and access to UHCI


And so, as I said earlier, UHCI works through ports, which means that we need to know the base of the UHCI registers from PCI.



At offset 0x20 is 4 bytes - IO Base. Regarding IO Base, we can use the following registers:



UHCI registers



Structures


Frame list pointer




Transfer Descrptor




TD CONTROL AND STATUS
. Bits:

TD Token


Queue head




Code


Initialize and configure HC:

PciBar bar; PciGetBar(&bar, id, 4); if (~bar.flags & PCI_BAR_IO) { // Only Port I/O supported return; } unsigned int ioAddr = bar.u.port; UhciController *hc = VMAlloc(sizeof(UhciController)); hc->ioAddr = ioAddr; hc->frameList = VMAlloc(1024 * sizeof(u32) + 8292); hc->frameList = ((int)hc->frameList / 4096) * 4096 + 4096; hc->qhPool = (UhciQH *)VMAlloc(sizeof(UhciQH) * MAX_QH + 8292); hc->qhPool = ((int)hc->qhPool / 4096) * 4096 + 4096; hc->tdPool = (UhciTD *)VMAlloc(sizeof(UhciTD) * MAX_TD + 8292); hc->tdPool = ((int)hc->tdPool / 4096) * 4096 + 4096; memset(hc->qhPool, 0, sizeof(UhciQH) * MAX_QH); memset(hc->tdPool, 0, sizeof(UhciTD) * MAX_TD); memset(hc->frameList, 0, 4 * 1024); // Frame list setup UhciQH *qh = UhciAllocQH(hc); qh->head = TD_PTR_TERMINATE; qh->element = TD_PTR_TERMINATE; qh->transfer = 0; qh->qhLink.prev = &qh->qhLink; qh->qhLink.next = &qh->qhLink; hc->asyncQH = qh; for (uint i = 0; i < 1024; ++i) hc->frameList[i] = 2 | (u32)(uintptr_t)qh; IoWrite16(hc->ioAddr + REG_INTR, 0); IoWrite16(hc->ioAddr + REG_CMD, IoRead16(hc->ioAddr + REG_CMD)&(~1)); unsigned short cfg = PciRead16(id, 4); PciWrite16(id, 4, cfg & (~1)); PciWrite16(id, 0x20, (short)-1); unsigned short size = ~(PciRead16(id, 0x20)&(~3)) + 1; PciWrite16(id, 0x20, hc->ioAddr); PciWrite16(id, 4, cfg | 5); // Disable Legacy Support IoWrite16(hc->ioAddr + REG_LEGSUP, 0x8f00); // Disable interrupts IoWrite16(hc->ioAddr + REG_INTR, 0); // Assign frame list IoWrite16(hc->ioAddr + REG_FRNUM, 0); IoWrite32(hc->ioAddr + REG_FRBASEADD, (int)hc->frameList); IoWrite16(hc->ioAddr + REG_SOFMOD, 0x40); // Clear status IoWrite16(hc->ioAddr + REG_STS, 0xffff); // Enable controller IoWrite16(hc->ioAddr + REG_CMD, 0x1); // Probe devices UhciProbe(hc, size); 

Endpoint Requests and Control Requests:

 // ------------------------------------------------------------------------------------------------ static void UhciDevControl(UsbDevice *dev, UsbTransfer *t) { UhciController *hc = (UhciController *)dev->hc; UsbDevReq *req = t->req; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint endp = 0; uint maxSize = dev->maxPacketSize; uint type = req->type; uint len = req->len; // Create queue of transfer descriptors UhciTD *td = UhciAllocTD(hc); if (!td) { return; } UhciTD *head = td; UhciTD *prev = 0; // Setup packet uint toggle = 0; uint packetType = TD_PACKET_SETUP; uint packetSize = sizeof(UsbDevReq); UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, req); prev = td; // Data in/out packets packetType = type & RT_DEV_TO_HOST ? TD_PACKET_IN : TD_PACKET_OUT; u8 *it = (u8 *)t->data; u8 *end = it + len; while (it < end) { td = UhciAllocTD(hc); if (!td) { return; } toggle ^= 1; packetSize = end - it; if (packetSize > maxSize) { packetSize = maxSize; } UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, it); it += packetSize; prev = td; } // Status packet td = UhciAllocTD(hc); if (!td) { return; } toggle = 1; packetType = type & RT_DEV_TO_HOST ? TD_PACKET_OUT : TD_PACKET_IN; UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, 0, 0); // Initialize queue head UhciQH *qh = UhciAllocQH(hc); UhciInitQH(qh, t, head); // Wait until queue has been processed UhciInsertQH(hc, qh); UhciWaitForQH(hc, qh); } // ------------------------------------------------------------------------------------------------ static void UhciDevIntr(UsbDevice *dev, UsbTransfer *t) { UhciController *hc = (UhciController *)dev->hc; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint endp = t->endp->desc->addr & 0xf; // Create queue of transfer descriptors UhciTD *td = UhciAllocTD(hc); if (!td) { t->success = false; t->complete = true; return; } UhciTD *head = td; UhciTD *prev = 0; // Data in/out packets uint toggle = t->endp->toggle; uint packetType = TD_PACKET_IN; //Here for compiler, on some last expression hadn't worked if (t->endp->desc->addr & 0x80) packetType = TD_PACKET_IN; else packetType = TD_PACKET_OUT; uint packetSize = t->len; UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, t->data); // Initialize queue head UhciQH *qh = UhciAllocQH(hc); UhciInitQH(qh, t, head); // Schedule queue UhciInsertQH(hc, qh); if(t->w) UhciWaitForQH(hc, qh); } 

Source: https://habr.com/ru/post/429422/


All Articles