EHCI Problems

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Hoozim
Member
Member
Posts: 53
Joined: Fri Jul 23, 2010 8:26 am

EHCI Problems

Post by Hoozim »

Hello All,

I am having problems with my EHCI driver. It for the most part seems to works fine (and has for the last year). Lately, I have been working with larger files (100s of kilobytes instead of just a few). The EHCI driver does a thousand or so transactions (it varies greatly), and then freezes on the wait for USB interrupt. I have included the file below with the driver. The method I am talking about is PerformTransactions(). I have deleted some methods I believe not to be causing the problems. I have tried adding delays into performing transactions but that doesn't help. I added a really rough timeout in the method and scaled it back. The timeout resets the port, reconfigures it and continues on as if nothing happened. The problem arises when it continues loading the file. It is able to do the 19 configuration transactions (including some on the bulk endpoint) perfectly. It only fails when it is back to parsing the file.

Code: Select all

#include "EHCIPrivate.h"


void EHCI::Private::ResetEHCI()
{
	
	EHCI::Private::OpRegister->USBCMD.RunStop = 0;
	while (EHCI::Private::OpRegister->USBSTS.HCHalted == 0)
	{
		PIT::WaitMilliseconds(30);
	}
	EHCI::Private::OpRegister->USBCMD.HostControllerReset = 1;
	while (EHCI::Private::OpRegister->USBCMD.HostControllerReset == 1)
	{
		PIT::WaitMilliseconds(20);
	}
	

	EHCI::Private::OpRegister->CTRLDSSEGMENT = 0;
	EHCI::Private::EnableInterrupts();
	EHCI::Private::OpRegister->PERIODICLISTBASE = 0;
	//EHCI::Private::OpRegister->USBCMD.InterruptThresholdControl = 8;
	EHCI::Private::OpRegister->ASYNCLISTADDR = 0;
	EHCI::Private::OpRegister->USBCMD.RunStop = 1;
	while (EHCI::Private::OpRegister->USBSTS.HCHalted == 1)
	{
		PIT::WaitMilliseconds(20);
	}
	EHCI::Private::OpRegister->CONFIGFLAG.ConfigureFlag = 1;
	PIT::WaitMilliseconds(50);
}




void EHCI::Private::EHCIIRQHandler()
{
	_asm add esp, 12;
	_asm pushad;
	_asm sti;

	if (EHCI::Private::OpRegister->USBSTS.USBINT)
	{
		EHCI::Private::USBInterrupt = true;
		EHCI::Private::OpRegister->USBSTS.USBINT = 1;
	}
	if (EHCI::Private::OpRegister->USBSTS.USBERRINT)
	{
		Video::Print("\nUSB Error Interrupt\n");
		EHCI::Private::OpRegister->USBSTS.USBERRINT = 1;
	}
	if (EHCI::Private::OpRegister->USBSTS.PortChangeDetect)
	{
		EHCI::Private::OpRegister->USBSTS.PortChangeDetect = 1;
		OutPort(0xA0, 0x20);
		OutPort(0x20, 0x20);
		PIT::WaitMilliseconds(30);
		EHCI::Private::ResetPorts();
		_asm popad;
		_asm iretd;
	}
	if (EHCI::Private::OpRegister->USBSTS.FrameListRollover)
	{
		Video::Print("\nFrame List Rollover\n");
		EHCI::Private::OpRegister->USBSTS.FrameListRollover = 1;
	}
	if (EHCI::Private::OpRegister->USBSTS.HostSystemError)
	{
		Video::Print("\nHost System Error\n");
		Video::Print("Halting system...");
		EHCI::Private::OpRegister->USBSTS.HostSystemError = 1;
		_asm cli;
		_asm hlt;
	}
	if (EHCI::Private::OpRegister->USBSTS.InterruptOnAsyncAdvance)
	{
		Video::Print("\nInterrupt on Async Advance\n");
		EHCI::Private::OpRegister->USBSTS.InterruptOnAsyncAdvance = 1;
	}
	OutPort(0xA0, 0x20);
	OutPort(0x20, 0x20);
	_asm popad;
	_asm iretd;
}

EHCI::Private::PORTSC* EHCI::Private::SelectPortSC(byte port)
{
	dword* NewAddress = (dword*)(EHCI::Private::PORTSCBaseAddress + (port * 4));
	return (EHCI::Private::PORTSC*)NewAddress;
}

void EHCI::Private::ResetPort(EHCI::Private::PORTSC* port)
{
		dword tempINTR = 0;
		memcpy(&tempINTR, (void*)&EHCI::Private::OpRegister->USBINTR, sizeof(EHCI::Private::OpRegister->USBINTR));
		port->PortReset = 1;
		PIT::WaitMilliseconds(300);
		port->PortReset = 0;
		while (port->PortReset == 1)
		{
			PIT::WaitMilliseconds(20);
		}
		memcpy((void*)&EHCI::Private::OpRegister->USBINTR, &tempINTR, sizeof(EHCI::Private::OpRegister->USBINTR));
}

void EHCI::ResetPort(byte i)
{
	EHCI::Private::PORTSC* port = EHCI::Private::SelectPortSC(i);
	EHCI::Private::ResetPort(port);
}

void EHCI::Private::ResetPorts()
{
	for (int i = 0; i < EHCI::Private::NumberOfPorts; i++)
	{
		EHCI::Private::PORTSC* port = EHCI::Private::SelectPortSC(i);
		if (port->CurrentConnectStatus)
		{
			if (EHCI::Private::PortConnectStatus[i] == 0)
			{
				EHCI::Private::ResetPort(port);
				EHCI::Private::PortConnectStatus[i] = USB20::ConfigureDevice(i);
			}
		}
		else
		{
			if (EHCI::Private::PortConnectStatus[i] != 0)
			{
				USB20::DeConfigureDevice(EHCI::Private::PortConnectStatus[i]);
				EHCI::Private::PortConnectStatus[i] = 0;
			}
		}
	}
}

static volatile int CoolDown = 0;

void EHCI::Private::PerformTransaction(dword AsyncAddress, bool wait, byte port)
{
	dword timeout = 100;
	timeout = 100;

	EHCI::Private::USBInterrupt = false;
	EHCI::Private::OpRegister->ASYNCLISTADDR = VirtualMemory::PhysicalAddress(AsyncAddress);
	EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 1;
	
	//while (EHCI::Private::OpRegister->USBSTS.AsynchronousScheduleStatus == 0)
	//{
		//PIT::WaitMilliseconds(5); // 5
	//}


	if (wait)
		PIT::WaitMilliseconds(50);
	else
	{
	}

	_asm hlt;
	Video::Print(" a ");
	while (!EHCI::Private::USBInterrupt)
	{
		PIT::WaitMilliseconds(10); // 5
		timeout--;
		if (timeout <= 1 && !wait)
		{
			Video::Print("  ehci   ");
			EHCI::ResetPort(port);
			USB20::DeConfigureDevice(EHCI::Private::PortConnectStatus[port]);
			EHCI::Private::PortConnectStatus[port] = 0;
			EHCI::Private::PortConnectStatus[port] = USB20::ConfigureDevice(port);

			EHCI::Private::OpRegister->USBSTS.USBINT = 1;
			EHCI::Private::USBInterrupt = false;
			PIT::WaitMilliseconds(300);
			timeout = 100;
			EHCI::Private::PerformTransaction(AsyncAddress, wait, port);
			return;
			break;
		}
	}

	EHCI::Private::USBInterrupt = false;

	//EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 0;
	//while (EHCI::Private::OpRegister->USBSTS.AsynchronousScheduleStatus == 1)
	//{
		//PIT::WaitMilliseconds(10); //5
		//_asm hlt;
	//}
	//EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 0;
	CoolDown++;
	Video::PrintNumber(CoolDown, 10);Video::Print("   ");
	return;
}

EHCI::Private::QTD* EHCI::Private::AllocateQTD(dword next)
{
	EHCI::Private::QTD* qtd = (EHCI::Private::QTD*)Heap::Malloc(sizeof(EHCI::Private::QTD), 32);
	memset(qtd, 0, sizeof(EHCI::Private::QTD));
	if (next == 0)
		qtd->NextQTDPointer = 1;
	else
		qtd->NextQTDPointer = VirtualMemory::PhysicalAddress(next);
	return qtd;
}

dword EHCI::Private::AllocateQTDBuffer(EHCI::Private::QTD* qtd)
{
	void* data = Heap::Malloc(4096, 4096);
	memset(data, 0, 4096);
	qtd->BufferPointer0 = VirtualMemory::PhysicalAddress((dword)data);
	qtd->BufferPointer1 = qtd->BufferPointer2 = qtd->BufferPointer3 = qtd->BufferPointer4 = 0;
	return (dword)data;
}

void EHCI::Private::CreateQueueHead(void* QHAddr, dword HorizontalPointer, void* FirstQTD, byte DeviceAddr, byte EndpointNum, dword MaxPacketSize)
{
	EHCI::Private::QueueHead* qh = (EHCI::Private::QueueHead*)QHAddr;
	memset(QHAddr, 0, sizeof(EHCI::Private::QueueHead));

	qh->HorizontalPointer = VirtualMemory::PhysicalAddress(HorizontalPointer) | 2;

	qh->DeviceAddress = DeviceAddr;
	qh->InactivateOnNextTransaction = 0;
	qh->EndpointNumber = EndpointNum;
	qh->EndpointSpeed = 2; 
	qh->DTC = 1; 
	qh->H = 1;
	qh->MaximumPacketLength = MaxPacketSize;
	qh->ControlEndpointFlag = 0;
	qh->NakReloadFlag = 15;

	qh->SMask = 0;
	qh->CMask = 0;
	qh->HubAddr = 0;
	qh->PortNumber = 0;
	qh->Mult = 1;

	if (FirstQTD == 0)
	{
		qh->QTD.NextQTDPointer = 1;
	}
	else
	{
		qh->QTD.NextQTDPointer = VirtualMemory::PhysicalAddress((dword)FirstQTD);
	}
}

void* EHCI::Private::CreateSetupQTD(dword next, bool toggle, dword tokenBytes, dword type, dword requestID, dword valueLo, dword valueHi, dword index, dword length, dword* buff)
{
	byte SETUP = 2;

	EHCI::Private::QTD* qtd = EHCI::Private::AllocateQTD(next);
	qtd->AlternateNextQTDPointer = 1;
	qtd->Token.Status = 0x80;
	qtd->Token.PIDCode = 2;
	qtd->Token.ErrorCounter = 3;
	qtd->Token.CurrentPage = 0;
	qtd->Token.InterruptOnComplete = 1;
	qtd->Token.TotalBytestoTransfer = tokenBytes;
	qtd->Token.DataToggle = toggle;

	dword buffer = EHCI::Private::AllocateQTDBuffer(qtd);
	EHCI::Private::SetupData* request = (EHCI::Private::SetupData*)buffer;
	request->RequestType = type;
	request->RequestID = requestID;
	request->ValueHi = valueHi;
	request->ValueLow = valueLo;
	request->Index = index;
	request->Length = length;

	*buff = buffer;
	return (void*)qtd;
}

void* EHCI::Private::CreateIOQTD(dword next, byte direction, bool toggle, dword tokenBytes, dword *buff)
{
	byte OUT = 0;
	byte IN = 1;

	EHCI::Private::QTD* qtd = EHCI::Private::AllocateQTD(next);
	qtd->AlternateNextQTDPointer = 1;
	qtd->Token.Status = 0x80;
	qtd->Token.PIDCode = direction;
	qtd->Token.ErrorCounter = 3;
	qtd->Token.CurrentPage = 0;
	qtd->Token.InterruptOnComplete = 1;
	qtd->Token.TotalBytestoTransfer = tokenBytes;
	qtd->Token.DataToggle = toggle;
	
	dword buffer = EHCI::Private::AllocateQTDBuffer(qtd);
	*buff = buffer;
	return (void*)qtd;
}

byte EHCI::SetupTransaction(byte DevAddr, byte EndpointNum, word BytesToTransfer, byte RequestType, byte Request, byte ValueHi, byte ValueLow, word Index, byte* Data, byte direction, byte port)
{
	byte OUT = 0;
	byte IN = 1;
	byte SETUP = 2;

	void* virtAList = Heap::Malloc(sizeof(EHCI::Private::QueueHead), 32);
	EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 0;

	dword SetupBuffer = 0;
	dword DataBuffer = 0;

	
	if (BytesToTransfer == 0)
	{
		void* HandShakeQTD = 0;
		HandShakeQTD = EHCI::Private::CreateIOQTD(0, IN, 1, 0, &DataBuffer);
		Heap::Free((void*)DataBuffer);
		void* SetupQTD = EHCI::Private::CreateSetupQTD((dword)HandShakeQTD, 0, 8, RequestType, Request, ValueLow, ValueHi, Index, 0, &SetupBuffer);

		EHCI::Private::CreateQueueHead(virtAList, (dword)virtAList, SetupQTD, DevAddr, EndpointNum, 64);
		EHCI::Private::PerformTransaction((dword)virtAList, true, port);

		if ((((EHCI::Private::QTD*)SetupQTD)->Token.Status) != 0)
		{
			Video::Print("\nQTD Status: ");
			Video::PrintNumber(((EHCI::Private::QTD*)SetupQTD)->Token.Status, 10);
			Video::NewLine();
		}

		if (((EHCI::Private::QTD*)SetupQTD)->Token.Status != 0)
		{
			Video::Print("\nTransaction Error!!\n");
			Heap::Free((void*)SetupBuffer);
			Heap::Free((void*)virtAList);
			Heap::Free((void*)HandShakeQTD);
			Heap::Free((void*)SetupQTD);
			return 0;
		}

		Heap::Free((void*)SetupBuffer);
		Heap::Free((void*)virtAList);
		Heap::Free((void*)HandShakeQTD);
		Heap::Free((void*)SetupQTD);
		return 1;
	}

	void* HandShakeQTD = 0;
	if (direction == IN)
	{
		HandShakeQTD = EHCI::Private::CreateIOQTD(0, OUT, 1, 0, &DataBuffer);
	}
	else
	{
		HandShakeQTD = EHCI::Private::CreateIOQTD(0, IN, 1, 0, &DataBuffer);
	}
	Heap::Free((void*)DataBuffer);
	void* DataQTD = EHCI::Private::CreateIOQTD((dword)HandShakeQTD, direction, 1, BytesToTransfer, &DataBuffer);
	if (direction == OUT)
	{
		memcpy((void*)DataBuffer, Data, BytesToTransfer);
	}
	void* SetupQTD = EHCI::Private::CreateSetupQTD((dword)DataQTD, 0, 8, RequestType, Request, ValueLow, ValueHi, Index, BytesToTransfer, &SetupBuffer);

	EHCI::Private::CreateQueueHead(virtAList, (dword)virtAList, SetupQTD, DevAddr, EndpointNum, 64);
	EHCI::Private::PerformTransaction((dword)virtAList, true, port);

	if (direction == IN)
	{
		memcpy(Data, (void*)DataBuffer, BytesToTransfer);
	}

	if ((((EHCI::Private::QTD*)SetupQTD)->Token.Status) != 0)
	{
		Video::Print("\nQTD Status: ");
		Video::PrintNumber(((EHCI::Private::QTD*)SetupQTD)->Token.Status, 10);
		Video::NewLine();
	}

	if (((EHCI::Private::QTD*)SetupQTD)->Token.Status != 0)
	{
		Video::Print("\nTransaction Error!!\n");
		Heap::Free((void*)SetupBuffer);
		Heap::Free((void*)DataBuffer);
		Heap::Free((void*)virtAList);
		Heap::Free((void*)HandShakeQTD);
		Heap::Free((void*)DataQTD);
		Heap::Free((void*)SetupQTD);
		return 0;
	}

	Heap::Free((void*)SetupBuffer);
	Heap::Free((void*)DataBuffer);
	Heap::Free((void*)virtAList);
	Heap::Free((void*)HandShakeQTD);
	Heap::Free((void*)DataQTD);
	Heap::Free((void*)SetupQTD);
	return 1;
}

byte EHCI::BulkTransaction(byte DevAddr, byte EndpointNum, word BytesToTransfer, byte* data, byte direction, byte port, byte toggle, word MaxPacketSize)
{
	Retry:
	byte OUT = 0;
	byte IN = 1;

	void* virtAList = Heap::Malloc(sizeof(EHCI::Private::QueueHead), 32);
	//EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 0;

	dword DataBuffer = 0;

	void* DataQTD = EHCI::Private::CreateIOQTD(1, direction, toggle, BytesToTransfer, &DataBuffer);

	if (direction == OUT)
	{
		memcpy((void*)DataBuffer, data, BytesToTransfer);
	}

	EHCI::Private::CreateQueueHead(virtAList, (dword)virtAList, DataQTD, DevAddr, EndpointNum, MaxPacketSize);
	EHCI::Private::PerformTransaction((dword)virtAList, false, port);

	if (direction == IN)
	{
		memcpy(data, (void*)DataBuffer, BytesToTransfer);
	}


	Heap::Free((void*)DataBuffer);
	Heap::Free((void*)virtAList);
	Heap::Free((void*)DataQTD);

	return 1;
}

byte EHCI::MSDBulkSCSIRead(byte DevAddr, byte EndpointNumOut, byte EndpointNumIn, byte port, bool toggleIN, bool toggleOUT, word MaxPacketSize, byte* cbw, byte* data, byte* status)
{
	Retry:
	byte OUT = 0;
	byte IN = 1;

	void* virtAList = Heap::Malloc(sizeof(EHCI::Private::QueueHead), 32);
	void* virtAList2 = Heap::Malloc(sizeof(EHCI::Private::QueueHead), 32);
	//EHCI::Private::OpRegister->USBCMD.AsynchronousScheduleEnable = 0;

	dword DataBufferCBW = 0;
	dword DataBufferData = 0;
	dword StatusBuffer = 0;
	
	void* StatusQtd = EHCI::Private::CreateIOQTD(1, IN, !toggleIN, 13, &StatusBuffer);
	void* DataQtd = EHCI::Private::CreateIOQTD((dword)StatusQtd, IN, toggleIN, 512, &DataBufferData);
	void* CBWQtd = EHCI::Private::CreateIOQTD(1, OUT, toggleOUT, 31, &DataBufferCBW);

	memcpy((void*)DataBufferCBW, cbw, 31);
	
	EHCI::Private::CreateQueueHead(virtAList, (dword)virtAList, CBWQtd, DevAddr, EndpointNumOut, MaxPacketSize);
	EHCI::Private::CreateQueueHead(virtAList2, (dword)virtAList2, DataQtd, DevAddr, EndpointNumIn, MaxPacketSize);

	EHCI::Private::PerformTransaction((dword)virtAList, false, port);
	EHCI::Private::PerformTransaction((dword)virtAList2, false, port);

	memcpy(data, (void*)DataBufferData, 512);
	memcpy(status, (void*)StatusBuffer, 13);

	Heap::Free((void*)virtAList);
	Heap::Free((void*)virtAList2);
	Heap::Free((void*)DataBufferCBW);
	Heap::Free((void*)DataBufferData);
	Heap::Free((void*)StatusBuffer);
	Heap::Free((void*)StatusQtd);
	Heap::Free((void*)DataQtd);
	Heap::Free((void*)CBWQtd);

	return 1;
}
Thanks in advance,
Hoozim
Nable
Member
Member
Posts: 453
Joined: Tue Nov 08, 2011 11:35 am

Re: EHCI Problems

Post by Nable »

> _asm sti;
I still can't get (after reading your code several times) why do you enable interrupts in the beginning of IRQ handler.

IASDM vol. 3A, chapter 6.12.2:
NOTE
Because IA-32 architecture tasks are not re-entrant, an interrupt-
handler task must disable interrupts between the time it completes
handling the interrupt and the time it executes the IRET instruction.
This action prevents another interrupt from occurring while the
interrupt task’s TSS is still marked busy, which would cause a
general-protection (#GP) exception.
May be you call your irq handler in a very special way.. but iretd in the end of the handler tells me that there's no special way here..
Hoozim
Member
Member
Posts: 53
Joined: Fri Jul 23, 2010 8:26 am

Re: EHCI Problems

Post by Hoozim »

Hello,

Thank you for your reply. I enable interrupts at the beginning of the handler because on the port change interrupt, the port must be configured and the attached device must be mounted (assuming it is a mass storage device). That all requires interrupts. I am going to change the handler so that the 'sti' only occurs when the port change interrupt actually occurs since it is not needed for the other interrupts (therefore cli).

Hoozim
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: EHCI Problems

Post by Combuster »

Nable wrote:tasks are not re-entrant
Most people don't switch tasks on an interrupt (task gate) but reuse the same task (trap or interrupt gate)

That said, an interrupt gate disables interrupts (which causes them to be re-enabled automatically on iret), whereas trap gates don't touch IF - you should be using the latter if you want interrupt handlers to execute with interrupts enabled.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
Post Reply