Розробка

UHCI, або найперший USB


Доброго часу доби, дорогий читачу! Мене просили написати про UHCI — добре, пишу.

Можливо, вам знадобитися ця стаття, якщо, приміром, ви не маєте достатніх навичок написання драйверів і читання документації до хардвейру. Простий приклад: хочете написати свою ОС для міні-ПК, щоб яка-небудь вінда або черговий дистрибутив лінукса не завантажували залізо, і ви використовували всю його міць виключно в своїх цілях.

Що таке UHCI?

Думаю, щоб ще раз не розпорошуватися на тему що і навіщо, просто залишу посилання на мою попередню статтю про EHCI. Тик сюди
UHCI — Universal Host Controller Interface, працює як PCI-пристрій, але, на відміну від EHCI використовує порти замість MMIO(Memory-Mapped-IO).

Терміни, які будуть використані далі

 

  • USB Driver (USBD) — сам USB драйвер
  • HC(Host Controller) — хост-контролер, або ж просто наш UHCI
  • Host Controller Driver (HCD) — драйвер, який зв’язує залізо і USBD
  • USB Device — саме USB-пристрій

Типи передачі даних

Isochronous — изосинхронная передача, яка має задану частоту передачі даних. Може бути використана, наприклад, для USB-мікрофонів і т. п.
Interrupt — Невеликі, спонтанні передачі даних з пристрою. Тип передачі переривання підтримує пристрої, які вимагають передбачуваного інтервалу обслуговування, але не обов’язково забезпечують передбачуваний потік даних. Зазвичай використовуються для таких пристроїв, як клавіатура і вказівні пристрої, які можуть не видавати дані протягом тривалих періодів часу, але вимагають швидкого відповіді, коли у них є дані для відправки.
Control — Тип передачі інформації про стан пристрою, стан і конфігурації. Тип передачі Control використовується для забезпечення каналу управління з пристроїв Host to USB. Control-передачі завжди складаються з фази налаштування і нуля або більше фаз даних, за якими слідує фаза стану. Вкрай важливо, щоб передача управління в задану кінцеву точку оброблялася в режимі FIFO. Якщо управління передається на одну і ту ж кінцеву точку, чергування може призвести до непередбачуваного поведінки.
Bulk — тип передачі масивів даних. Використовується, наприклад в MassStorage-пристроях.

Ось так виглядає розподіл 1мс часу — обробка одного кадру.

Розподіл часу

Контролер хоста підтримує доставку даних в реальному часі, генеруючи пакет Start Of Frame (SOF) кожні 1 мс. SOF-пакет генерується, коли закінчується лічильник SOF в хост-контролера (рис. 3). Контролер хоста ініціалізує лічильник SOF для часу кадру 1 мс. Можуть бути внесені невеликі зміни в це значення (і, отже, період часу кадру) шляхом програмування регістра зміни SOF. Ця функція дозволяє внести незначні зміни в період часу кадру, якщо це необхідно, для підтримки синхронізації в реальному часі у всій системі USB.
Контролер хоста включає в себе номер кадру в кожному SOF-пакеті. Цей номер кадру однозначно визначає період кадру в реальному часі. Умова закінчення кадру (EOF) виникає наприкінці часового інтервалу 1 мс, коли хост-контролер починає наступне час кадру, генеруючи ще один SOF-пакет з відповідним номером кадру. Протягом періоду кадру дані передаються у вигляді пакетів інформації. Період часу кадру суворо дотримується хост-контроллером, а пакети даних в поточному кадрі не можуть виходити за межі EOF (див. Главу 11 в специфікації USB). Контролер хоста підтримує синхронізацію передачі даних між кадрами в реальному часі, прив’язуючи номер кадру до виконання конкретної запису в списку кадрів. Лічильник кадрів хост-контролера генерує номер кадру (11-бітне значення) і включає його в кожен пакет SOF. Лічильник програмується через регістри і збільшується кожен період кадру. Контролер хоста використовує молодші 10 біт номера кадру в якості індексу в списку кадрів з 1024 фреймами, який зберігається в системній пам’яті. Таким чином, оскільки лічильник кадрів керує вибором запису зі списку кадрів, хост-контролер обробляє кожну запис в списку в заданий період кадру. Контролер хоста збільшується до наступного запису в списку кадрів для кожного нового кадру. Це гарантує, що ізохронні передачі виконуються у визначеному кадрі.
Малюнок 3:

UHCI структури

Тут все точно так само, як і з EHCI. Приклад запитів до HC:

Налаштування і доступ до UHCI

І так, як я вже сказав раніше, UHCI працює через порти, значить від PCI нам треба дізнатися базу регістрів UHCI.

По зсуву 0x20 лежить 4 байти — IO Base. Щодо IO Base ми можемо скористатися наступними регістрами:

Регістри UHCI

 

  • USBCMD — регістр для управління HC’ом. Біти:
    • Біт 6 — прапор, що пристрій налаштовано і ініціалізований успішно.
    • Біт 1 — HC Reset. Встановлюється для скидання HC’а.
    • Біт 0 — Run/Stop. Відображає стан HC. 1 — працює, 0 — немає.
  • USBSTS — Регістр статусу. Біти:
    • Біт 5 — HC Halted. Сталася помилка, або ж контролер успішно виконав HC Reset.
    • Біт 4 — Host Process Controller Error. Біт встановлюється в 1 коли сталась критична помилка і HC не може продовжити виконання черг і TD.
    • Біт 3 — Host System Error. Помилка PCI.
    • Біт 1 — Error Interrupt. Показує те, що сталася помилка і HC згенерував переривання.
    • Біт 0 — Interrupt. Показує, що HC згенерував переривання.
  • USBINTR — Регістр налаштування переривань. Біти:
    • Біт 2 — IOC — Interrupt on complete — генерує переривання при завершенні транзакції.
  • FRNUM — Номер поточного кадру(Брати його & 0x3FF для правильного значення).
  • FLBASEADD — Frame List Base Address — адреса списку фреймів.
  • PORTSC — Port status and control — регістр статусу і управління портом. Біти:
    • Біт 9 — Port Reset — 1 – порт ресетиться.
    • Біт 8 — показує, що до порту підключено Low-speed пристрій
    • Біт 3 — показує, що стан включеності порту змінено
    • Біт 2 — показує, включений порт
    • Біт 1 — показує, що змінено стан подключенности пристрою до порту
    • Біт 0 — показує, що пристрій підключено до порту.

Структури

 

Frame List Pointer

Transfer Descrptor

TD CONTROL AND STATUS

. Біти:

  • Біти 28-27 — лічильник помилок, аналогічно EHCI.
    • Біт 26 — 1=Low-speed пристрій, 0=Full-speed пристрій.
    • Біт 25 — 1=изосинхроный TD
    • Біт 24 — IOC
    • Біти 23-16 — статус:
    • Біт 23 — Показує те, що це активний TD
    • Біт 22 — Stalled
    • Біт 21 — Data Buffer Error
    • Біт 20 — Babble Detected
    • Біт 19 — NAK
  • Біти 10-0: кількість байтів, переданих хост-контролером.

 

TD Token

 

  • Біти 31:21 — Max Packet Len, аналогічно EHCI
  • Біт 19 — Data Toggle, аналогічно EHCI
  • Біти 18:15 — Номер кінцевої точки
  • Біти 18:14 — адресу пристрою
  • Біти 7:0 — PID. In=0x69, Out = 0xE1, Setup=0x2D

 

Queue Head

Код

Ініціалізація та налаштування 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);

Запити до кінцевих точок і запити:

// ------------------------------------------------------------------------------------------------
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 is invalid
 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 is invalid
 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 compiler for, 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);
}

Related Articles

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close