Page 1 of 2

Am I detecting PCI interrupt routing correctly?

Posted: Sun Sep 18, 2016 3:14 pm
by mariuszp
My approach appears to work in VirtualBox, Bochs and QEMU, but not on my laptop.

The relevant part of the code is as follows:

Code: Select all

static ACPI_STATUS pciWalkCallback(ACPI_HANDLE object, UINT32 nestingLevel, void *context, void **returnvalue)
{
	ACPI_DEVICE_INFO *info;
	ACPI_STATUS status = AcpiGetObjectInfo(object, &info);
	
	if (status != AE_OK)
	{
		panic("AcpiGetObjectInfo failed");
	};
	
	if (info->Flags & ACPI_PCI_ROOT_BRIDGE)
	{
		foundRootBridge = 1;
		
		ACPI_BUFFER prtbuf;
		prtbuf.Length = ACPI_ALLOCATE_BUFFER;
		prtbuf.Pointer = NULL;
		
		status = AcpiGetIrqRoutingTable(object, &prtbuf);
		if (status != AE_OK)
		{
			panic("AcpiGetIrqRoutingTable failed for a root bridge!\n");
		};
     /* ... parsing the information ... */
};
And I call ACPICA's enumeration function as follows:

Code: Select all

void pciInitACPI()
{
	void *retval;
	ACPI_STATUS status = AcpiGetDevices(NULL, pciWalkCallback, NULL, &retval);
	if (status != AE_OK)
	{
		panic("AcpiGetDevices failed");
	};
	
	if (!foundRootBridge)
	{
		panic("failed to find PCI root bridge");
	};
	
	pciIntInitDone = 1;
};
And when running it on my laptop, I get the "AcpiGetIrqRoutingTable failed for a root bridge!\n" message. ACPICA also claims that "there is no handler for region ERAM".

Is this a firmware bug on the laptop, or am I doing something wrong?

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 3:09 am
by MollenOS
I don't think you fully setup ACPI correctly before enumeration. I do ALL this before calling enumeration:

Code: Select all

/* Initializes the full access and functionality
 * of ACPICA / ACPI and allows for scanning of
 * ACPI devices */
void AcpiInitialize(void)
{
	ACPI_STATUS Status;
	ACPI_OBJECT arg1;
	ACPI_OBJECT_LIST args;

	/* Debug */
	LogInformation("ACPI", "Initializing");
	LogInformation("ACPI", "Installing OSI Interface");
	
	/* Install OSL Handler */
	Status = AcpiInstallInterfaceHandler(AcpiOsi);

	/* We fake Windows 7 */
	AcpiOsiSetup("Windows 2009");

	/* Install */
	AcpiOsiInstall();

	/* Initialize the ACPICA subsystem */
	LogInformation("ACPI", "Installing Subsystems");
	Status = AcpiInitializeSubsystem();
	if (ACPI_FAILURE(Status))
	{
		LogFatal("ACPI", "Failed to initialize subsystems, %u!", Status);
		for (;;);
	}

	/* Copy the root table list to dynamic memory */
	LogInformation("ACPI", "Reallocating Tables");
	Status = AcpiReallocateRootTable();
	if (ACPI_FAILURE(Status))
	{
		LogFatal("ACPI", "Failed AcpiReallocateRootTable, %u!", Status);
		for (;;);
	}

	/* Install the default address space handlers. */
	Status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
		ACPI_ADR_SPACE_SYSTEM_MEMORY, ACPI_DEFAULT_HANDLER, NULL, NULL);
	if (ACPI_FAILURE(Status)) {
		LogDebug("ACPI", "Could not initialise SystemMemory handler, %s!", 
			AcpiFormatException(Status));
	}

	Status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
		ACPI_ADR_SPACE_SYSTEM_IO, ACPI_DEFAULT_HANDLER, NULL, NULL);
	if (ACPI_FAILURE(Status)) {
		LogDebug("ACPI", "Could not initialise SystemIO handler, %s!",
			AcpiFormatException(Status));
	}

	Status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
		ACPI_ADR_SPACE_PCI_CONFIG, ACPI_DEFAULT_HANDLER, NULL, NULL);
	if (ACPI_FAILURE(Status)) {
		LogDebug("ACPI", "Could not initialise PciConfig handler, %s!",
			AcpiFormatException(Status));
	}

	/* Create the ACPI namespace from ACPI tables */
	LogInformation("ACPI", "Loading Tables");
	Status = AcpiLoadTables();
	if (ACPI_FAILURE(Status))
	{
		LogFatal("ACPI", "Failed LoadTables, %u!", Status);
		for (;;);
	}

	/* Initialize the ACPI hardware */
	LogInformation("ACPI", "Enabling Subsystems");
	Status = AcpiEnableSubsystem(ACPI_FULL_INITIALIZATION);
	if (ACPI_FAILURE(Status))
	{
		LogFatal("ACPI", "Failed AcpiEnableSubsystem, %u!", Status);
		for (;;);
	}
	
	/* Probe for EC here */

	/* Complete the ACPI namespace object initialization */
	LogInformation("ACPI", "Initializing Objects");
	Status = AcpiInitializeObjects(ACPI_FULL_INITIALIZATION);
	if (ACPI_FAILURE(Status))
	{
		LogFatal("ACPI", "Failed AcpiInitializeObjects, %u!", Status);
		for (;;);
	}

	/* Run _OSC on root, it should always be run after InitializeObjects */
	AcpiCheckBusOscSupport();

	/* Set APIC Mode */
	arg1.Type = ACPI_TYPE_INTEGER;
	arg1.Integer.Value = 1;				/* 0 - PIC, 1 - IOAPIC, 2 - IOSAPIC */
	args.Count = 1;
	args.Pointer = &arg1;

	AcpiEvaluateObject(ACPI_ROOT_OBJECT, "_PIC", &args, NULL);

	/* Info */
	LogInformation("ACPI", "Installing Event Handlers");

	/* Install a notify handler */
	AcpiInstallNotifyHandler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY, AcpiBusNotifyHandler, NULL);

	/* Install a global event handler */
	AcpiInstallGlobalEventHandler(AcpiEventHandler, NULL);
	//AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON, acpi_shutdown, NULL);
	//AcpiInstallFixedEventHandler(ACPI_EVENT_SLEEP_BUTTON, acpi_sleep, NULL);
	//ACPI_BUTTON_TYPE_LID

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 4:56 am
by mariuszp
Thank you. Another related question; if I find the PCI root bridge (as I do above), it will only give me the IRQ routing information for devices on PCI bus 0, right? In this case, how do I find interrupt routing for the other buses?

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 6:24 am
by MollenOS
No, the pci root bus will in most cases contain all the irq routings (unless there is multiple pci roots). But, devices behind pci bridges need to have their pci routings sizzled (look it up, pci bridges reroute in some cases, but this is something fixed) in most cases, and therefore you usually can't directly use the IRQ routing without changing it.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 10:56 am
by mariuszp
Now it gives me a problem here, in VirtualBox:

Code: Select all

			ACPI_PCI_ROUTING_TABLE *table = (ACPI_PCI_ROUTING_TABLE*) scan;
			if (table->Length == 0)
			{
				break;
			};
			
			uint8_t slot = (uint8_t) (table->Address >> 16);
			if (table->Source == 0)
			{
				// static assignment
				pciMapInterruptFromGSI(0, slot, table->Pin+1, table->SourceIndex);
			}
			else
			{
				// get the PCI Link Object
				ACPI_HANDLE linkObject;
				status = AcpiGetHandle(object, table->Source, &linkObject);
				if (status != AE_OK)
				{
					panic("AcpiGetHandle failed for '%s'", table->Source);
				};
AcpiGetHandle fails, as table->Source is an empty string.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 11:10 am
by MollenOS

Code: Select all

if (table->Source[0] == '\0')
Is the check you should make

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 11:22 am
by mariuszp
Well, now it doesn't panic but it appears that my network driver lost the ability to receive interrupts. It also seems suspicious since now I'm clearly getting GSIs instead of LINK objects for the interrupt routes.

EDIT: Here's the code for attaching a PCI device to GSI:

Code: Select all

static void pciMapInterruptFromGSI(uint8_t bus, uint8_t slot, uint8_t intpin, int gsi)
{
	kprintf("PCI %d/%d INT%c# -> GSI %d\n", (int) bus, (int) slot, 'A'+intpin, gsi);
	static int nextLocalInt = 0;
	
	int i;
	for (i=0; i<gsiMapSize; i++)
	{
		if (gsiMapCache[i].gsi == gsi)
		{
			pciMapLocalInterrupt(bus, slot, intpin, gsiMapCache[i].intNo);
			return;
		};
	};
	
	int intNo = I_PCI0 + nextLocalInt;
	nextLocalInt = (nextLocalInt + 1) % 16;
	
	PCIGlobalInterruptMapping *newCache = (PCIGlobalInterruptMapping*) kmalloc(sizeof(PCIGlobalInterruptMapping)*(gsiMapSize+1));
	memcpy(newCache, gsiMapCache, sizeof(PCIGlobalInterruptMapping)*gsiMapSize);
	newCache[gsiMapSize].gsi = gsi;
	newCache[gsiMapSize].intNo = intNo;
	if (gsiMapCache != NULL) kfree(gsiMapCache);
	gsiMapCache = newCache;
	
	// remap the interrupt
	uint32_t volatile* regsel = (uint32_t volatile*) 0xFFFF808000002000;
	uint32_t volatile* iowin = (uint32_t volatile*) 0xFFFF808000002010;
	*regsel = (0x10+2*gsi);
	__sync_synchronize();
	uint64_t entry = (uint64_t)(intNo) | ((uint64_t)(apic->id) << 56);
	*iowin = (uint32_t) entry;
	__sync_synchronize();
	*regsel = (0x10+2*gsi+1);
	__sync_synchronize();
	*iowin = (uint32_t)(entry>>32);
	__sync_synchronize();
	
	// and register with the device
	pciMapLocalInterrupt(bus, slot, intpin, intNo);
};
Is this the correct way of doing this? I'm basically mapping the supplied interrupt number (from table->SourceIndex) to one of my local PCI IRQs (named I_PCI0 - I_PCI15) using IOAPIC. The device driver is triggered when the IRQs it was mapped to arrives. Could it be that multiple devices are connected to the same GSI?

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 11:45 am
by MollenOS
Uh, are you mapping your interrupts correctly with the correct set trigger / polarity? They should be set to Active Low, Level Triggered.

You should in most cases get GSI and not link objects. Only 1 in my 8 laptops have link objects.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 12:31 pm
by mariuszp
The behaviour I spoke about above happens in VirtualBox.
I've tried this on my laptop and it gets stuck either on AcpiLoadTable() or AcpiEnableSubsystem(). I'm going to do some extra debugging and post another respoonse.

And I'll check if the polarity is correct.

Thanks for the help so far.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 1:31 pm
by MollenOS
If it fails under the Acpi* Functions, it's probably because your glue-functions (or - LibC functions) are implemented wrong or incorrectly.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Mon Sep 19, 2016 2:32 pm
by mariuszp
MollenOS wrote:If it fails under the Acpi* Functions, it's probably because your glue-functions (or - LibC functions) are implemented wrong or incorrectly.
That is very possible, so I'm going to debug it more to see why that is the case.

Also, it appears that multiple INTx# pins were connected to the same GSI, and my OS failed to recognise that due to a subtle bug, and the polarity and trigger were both wrong! But that is fixed now, and interrupts are working in VirtualBox again.

So now I just have to figure out why the Acpi functions get stuck and hopefully it will run on my laptop.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Wed Sep 21, 2016 7:36 am
by mariuszp
I've done some debugging and noticed something strange. I noticed it hangs on an AcpiOsWaitSemaphore(), so I made that function print a stack trace every time it's called, and it hanged on this:
14393255_1654373174874476_431390092_o.jpg
I then made it print a message whenever that function is called with that specific semaphore address and got this:
14394004_1654661588178968_2051502666_o.jpg
where the address after "waited by..." is the thread description. So it appears that the same ACPICa thread tries to lock the same mutex twice; I've also found that it is the ACPI_MTX_NAMESPACE.

What could be the reason behind this?

Re: Am I detecting PCI interrupt routing correctly?

Posted: Wed Sep 21, 2016 9:11 am
by MollenOS
I would suspect your implementation of AcpiOsWaitSemaphore is incorrect

Re: Am I detecting PCI interrupt routing correctly?

Posted: Wed Sep 21, 2016 9:43 am
by mariuszp
MollenOS wrote:I would suspect your implementation of AcpiOsWaitSemaphore is incorrect
That wouldn't explain why it's called twice without the signal function being called.

The implementation is as follows:

Code: Select all

ACPI_STATUS AcpiOsWaitSemaphore(ACPI_SEMAPHORE sem, UINT32 units, UINT16 timeout)
{
	TRACE();
	//kprintf("AcpiOsWaitSemaphore(%p, %u, %hu)\n", sem, units, timeout);
	//stackTraceHere();
	if (sem == (void*)0xFFFF810000290E80)
	{
		kprintf("Semaphore %p waited by %p\n", sem, getCurrentThread());
	};
	
	int count = (int) units;
	if (sem == NULL)
	{
		return AE_BAD_PARAMETER;
	};
	
	if (units == 0)
	{
		return AE_OK;
	};

	int flags;
	uint64_t nanoTimeout;
	
	if (timeout == 0)
	{
		flags = SEM_W_NONBLOCK;
		nanoTimeout = 0;
	}
	else if (timeout == 0xFFFF)
	{
		flags = 0;
		nanoTimeout = 0;
	}
	else
	{
		flags = 0;
		nanoTimeout = (uint64_t) timeout * 1000000;
	};
	
	uint64_t deadline = getNanotime() + nanoTimeout;
	int acquiredSoFar = 0;
	
	while (count > 0)
	{
		int got = semWaitGen(sem, count, flags, nanoTimeout);
		if (got < 0)
		{
			semSignal2(sem, acquiredSoFar);
			return AE_TIME;
		};
		
		acquiredSoFar += got;
		count -= got;
		
		uint64_t now = getNanotime();
		if (now < deadline)
		{
			nanoTimeout = deadline - now;
		}
		else if (nanoTimeout != 0)
		{
			semSignal2(sem, acquiredSoFar);
			return AE_TIME;
		};
	};
	
	return AE_OK;
};
The loop is there because semWaitGen() doesn't wait until ALL requested units are available; it waits until ANY are available and returns however many possible.

Re: Am I detecting PCI interrupt routing correctly?

Posted: Wed Sep 21, 2016 5:03 pm
by mariuszp
Does ACPICA need recursive mutexes or something? It clearly doesn't work when it implements mutexes with semaphores; after locking a mutex, it calls a function which then tries to lock the mutex again, as can be seen on the stack trace I've provided. I changes the AcpiExStartMethodTrace function so that it always fails to acquire the mutex but now something else hangs... has anyone else experienced a similar problem? This only happens on my TOSHIBA laptop, doesn't happen in VirtualBox.

EDIT: Apparently someone else had it hang like that too:
http://f.osdev.org/viewtopic.php?f=2&t=24229