Привет всем.
Балуюсь тут с UHCI. Пока просто пытаюсь из тестовой библиотечки получать дескрипторы устройств в корневом хабе. На qemu после долгих тряхомудий все наконец завелось, а на VMWare - никак.
Контроллер конфигурируется как по маслу, никаких ошибок не замечено. Порты тоже благополучно ресетятся и включаются. К моменту начала передач у порта состояние 0x85, как и должно быть. Обращаюсь к портам по очереди.
При передаче пакета SetAddress (первый же пакет) VMWare успешно берет фрейм, проходит по очереди (в очереди пока 1 указатель на TD), находит первый TD и после этого ставит в нем биты CRC/Timeout error и STALL, зануляет длину пакета и все. Сам контроллер не падает и продолжает нормальную работу. Ему можно в очередь добавить еще новые TD и получить еще кучу STALL.
Раз контроллер регулярно успешно доходит до TD, значит сам контроллер работает и находит все структурки в памяти. Получается, ошибка уже непосредственно при передаче буфера устройству. Но при передаче токен сформирован нормально (т.к. работает на qemu, да и руками все биты проверял), статус тоже.
Писал по образу кода kolibri (не идеал конечно, но все же рабочий код). У меня сейчас почти один-в-один (за исключением того, что у меня пока тестовый код, вместо прерываний там циклы ожидания).
Формирование первого же падающего SETUP токена:
((8 - 1) << 21) | (0 << 19) | 0x2D = E0002D. Что на vmware, что на qemu. 8 - размер данных для SETUP передачи, 0 - fullSpeed (этот 0 читается из порта устройства), 2d - константа из док.
Формирование статуса:
(1 << 23) | (3 << 27) | (0 << 26) | 0x7FF = 188007FF. 1 - ACTIVE флаг, 3 - допустимое кол-во ошибок, 0 - DATA0, 7FF - нулевой текущий размер (по докам это поле только для результата контроллера, так что на него пофиг).
В буфере (8 байт) лежит константа
(USB_SET_ADDRESS << 8) | (curAddr << 16) = 0x10500, т.к. нулевой байт для битмапа типа данных и в данном случае должен быть 0.
Говорят (не в доках, а просто люди), буфер должен не пересекать границу в 4 кб. У меня буфер в стеке, адрес 0x3fff30 и размером 8 байт.
Все константы я вывел непосредственно из TD перед постановкой его в очередь.
В чем может быть проблема?
На всякий случай, инициализация контроллера и перебор портов (почти совпадает с кодом kolibri).
Код:
// UHCI Init
uint16_t cfg;
uint16_t ioBase, ioSize;
SUsbUHCIContext *uhci;
ioBase = ReadPCIReg(pci, 0x20, PCI_SIZE_WORD) & (~3); // get IO base
uint16_t cmd = _inpw(ioBase + UHCI_USBCMD) & (~1);
_outpw(ioBase + UHCI_USBCMD, cmd); // stop controller
_outpw(ioBase + UHCI_USBINTR, 0); // disable interrupts
WritePCIReg(pci, UHCI_USBLEGSUP, PCI_SIZE_WORD, UHCI_USBLEGSUP_RWC); // disable bios handling
cfg = ReadPCIReg(pci, 4, PCI_SIZE_WORD);
WritePCIReg(pci, 4, PCI_SIZE_WORD, cfg & (~1)); // disable IO access
WritePCIReg(pci, 0x20, PCI_SIZE_WORD, -1);
ioSize = ~(ReadPCIReg(pci, 0x20, PCI_SIZE_WORD) & (~3)) + 1; // get IO size
WritePCIReg(pci, 0x20, PCI_SIZE_WORD, ioBase); // restore state
WritePCIReg(pci, 4, PCI_SIZE_WORD, cfg | 5); // enable IO and bus mastering
_outpw(ioBase + UHCI_USBCMD, UHCI_USBCMD_HCRESET); // reset device
dummySleep(10);
// reset all registers
uint32_t ioEnd = ioBase + ioSize;
uint32_t tbase = ioBase + UHCI_PORT1STATUS;
while (tbase < ioEnd)
{
int16_t val = _inpw(tbase);
if (val == 0xFFFF || !(val & 0x80))
break;
_outpw(tbase, 0);
tbase += 2;
}
uint16_t portCnt = (tbase - (ioBase + UHCI_PORT1STATUS)) >> 1;
if (!portCnt)
{
DSPrint("No ports on UHCI controller");
return 0;
}
_outpw(ioBase + UHCI_STATUSREG, 0x3f); // clear status
_outpw(ioBase + UHCI_FRAMENUM, 0); // clear frame number
uhci = createUHCI(ioBase, portCnt);
uint32_t uhciBase = GetPhysAddr(uhci);
_outpd(ioBase + UHCI_BASEADDRES, uhciBase); // set base address
_outpw(ioBase + UHCI_SOFMODITFY, 64);
_outpw(ioBase + UHCI_USBCMD, 0xC1); // start: out Run + Configured + MaxPacket is 64 bytes
dummySleep(10);
// loop through ports
for (uint64_t i = 0; i < uhci->portCnt; ++i)
{
uint16_t curPort = uhci->ioBase + UHCI_PORT1STATUS + i * 2;
uint16_t pstat = _inpw(curPort);
if ((pstat & 0x0A) == 0)
continue;
// clear status and read again
_outpw(curPort, pstat);
uint16_t stat = _inpw(curPort);
if ((pstat & 2) != 0)
{
// state changed
if (stat & 1)
{
//DSPrint("Device connected");
uhci->portStats |= (1 << i);
_outpw(curPort, stat | 2); // reset port
dummySleep(10);
uint16_t resSt = _inpw(curPort);
_outpw(curPort, resSt & (~(2 << 8))); // clear reset
dummySleep(1);
resSt = _inpw(curPort); // enable port, clear status change
_outpw(curPort, resSt | 6);
dummySleep(1);
resSt = _inpw(curPort);
if ((resSt & 1) == 0)
DSPrint("Port disabled after reset");
bool isLowspeed = (resSt >> 8) & 1;
uhciNewDevice(uhci, isLowspeed);
}
else
{
DSPrint("Device disconnected");
uhci->portStats &= ~(1 << i);
}
}
else
{
// state not changed
if ((pstat & 8) && !(stat & 4))
DSPrint("usb port disabled");
}
}
...
void uhciNewDevice(SUsbUHCIContext *uhci, bool isLowspeed)
{
uint64_t curAddr = usbAddr++;
SChannel *chan = createChannel(uhci, 0, 0, 64, USB_PIPE_CONTROL, isLowspeed);
insertChannel(chan);
char config[8];
*((uint64_t *)config) = (USB_SET_ADDRESS << 8) | (curAddr << 16);
usbControlTransfer(chan, (uint64_t *)config, 0, 0);
while (chan->lastTD->prev)
{
completeTransfers(chan);
dummySleep(10);
}
}
...
void usbControlTransfer(SChannel *chan, uint64_t *config, void *buf, uint16_t size)
{
addTransfer(chan, config, 8, 0, 0);
addTransfer(chan, 0, 0, 1, 1);
}
На реальном железе пока нет возможности проверить.
Нашел на английском форуме осдев
частично похожую проблему, но там никаких ответов нет уже 4 года.
Кстати, тег [SPOILER] возможно не помешал бы на форуме.