I keep mine pretty simple, most of the code for the determining what resources a device uses is done outside of the device itself and simply passed to the driver when it's initialized. The driver will request to install a handler for that event and the OS will install one, for user space drivers this will run the proper task through a callback as Combuster described, and await a response back saying either 'I generated this interrupt and have handled it', at which point the kernel will take care of the EOI and go on about it's merry way.
For kernel space drivers it's much the same, without the task switching and with more direct call backs. Some devices also handle interrupt events through other methods because they're required to. For example ACPI compatible embedded controllers must signal their interrupts through an SCI and ACPI's GPE interface (this is not the case in 5.0 on a 'hardware reduced' platform). So drivers attach instead to a GPE event interface, which ACPICA implements wonderfully.
In general I'd agree with the idea of just abstracting it into some catch all 'interrupt event' and letting the kernel, HAL or whatever other drivers take care of the implementation specific details. It makes it much easier to signal and deal with those situations where you have a bus, behind a bus, behind a bus as well. Another example from the world of ACPI, you could have an Smbus controller requiring attention which is behind an EC controller. Generally this will be signaled by a single SCI being generated, which could be shared by a huge number of devices. The ACPI driver figures out that it's a GPE generated from the embedded controller, which is then forced to issue a query command and report back. The query command will tell it that the SMBus controll is what actually generated the interrupt. From there the SMBus driver will actually have to check the SMBus controller and figure out what device behind the SMBus was asserting it's alarm signal. Let's say in the end it's a smart battery device, so that in the end is the one device that ended up asserting an SCI, but to figure it out you had to jump through all those hoops.
Obviously such a scenario doesn't fit in too cleanly with the idea of a numbered interrupt line on the processor or it's bus. You just have a hardware event that signaled a very large 'catch all' interrupt along the line. You pretty much have to abstract it into some event caused with a virtual id number, or an event controlled by the SMBus controller itself. Events let you cleanly abstract away the mess of how a particular device or driver actually recieved the event, and focus simply on handling it.
Abstracting the interrupt interface
Re: Abstracting the interrupt interface
Reserved for OEM use.
Re: Abstracting the interrupt interface
The way I do this is is fairly minimalistic - All 256 interrupts share essentially the same handler that looks up the interrupt number in a table, although interrupts 0 - 31 are considered "traps" and do not participate in this mechanism instead having fixed handlers that correspond to the usual x86 exceptions. For the remainder of the interrupts the table is checked to see if any process has registered to be alerted on that event and if so the corresponding message is inserted in the message queue.
In another system some sort of abstraction for issuing EOIs would probably be required, in this one the process can simply do so itself provided it has requested the necessary access.
The end result from user space perspective (such as user space actually exists in my application) looks something like this:
YMMV. My project is not actually an operating system, it's a bare-metal hypervisor but i've ended up with a very small microkernel underneath it, mostly for reasons of sanity. I wanted to avoid the usual messyness which occurs with the ring 0 parts of typical hosted design VMMs where depending on which context you are in, hardware interrupts might be taken by the VMM and forwarded to the kernel by the switcher or taken directly by the host OS. The astute will notice from the test case that my microkernel does not actually even have keyboard input yet (detecting the press of any key does not count) but I pasted it because it shows at least one person's developing idea of how to abstract this sort of thing. Traps and exceptions are not part of this interface, I decided not to allow direct handling of exceptions without kernel involvement as that would be a debugging nightmare.
In another system some sort of abstraction for issuing EOIs would probably be required, in this one the process can simply do so itself provided it has requested the necessary access.
The end result from user space perspective (such as user space actually exists in my application) looks something like this:
Code: Select all
/* HardIntNotify.c - test case for hardirq server notification API
*/
#include <uk.h>
#define _UM_
#include <hal.h> // fixme don't want user space processes linking to x86hal.lib
#include <dbgprint.h>
BYTE loop;
DWORD32 DispatchMessage(DWORD32 msg, PMSGPARAMLIST param)
{
switch (msg)
{
case MSG_HARDINT:
Hal8259Eoi();
loop = 0;
// fixme: syscalls from msg dispatch callback
// crash right now.
break;
}
}
DWORD32 main(char *cmdline)
{
loop = 1;
UkRegisterHardIntNotification(0x21);
DbgPrint("Hardware interrupt notification testcase\n");
DbgPrint("press a key");
do {
UkReleaseQuantum();
} while(loop);
DbgPrint("Success");
return EXIT_SUCCESS;
}
YMMV. My project is not actually an operating system, it's a bare-metal hypervisor but i've ended up with a very small microkernel underneath it, mostly for reasons of sanity. I wanted to avoid the usual messyness which occurs with the ring 0 parts of typical hosted design VMMs where depending on which context you are in, hardware interrupts might be taken by the VMM and forwarded to the kernel by the switcher or taken directly by the host OS. The astute will notice from the test case that my microkernel does not actually even have keyboard input yet (detecting the press of any key does not count) but I pasted it because it shows at least one person's developing idea of how to abstract this sort of thing. Traps and exceptions are not part of this interface, I decided not to allow direct handling of exceptions without kernel involvement as that would be a debugging nightmare.
- NickJohnson
- Member
- Posts: 1249
- Joined: Tue Mar 24, 2009 8:11 pm
- Location: Sunnyvale, California
Re: Abstracting the interrupt interface
I actually have an interesting way I've tried to abstract interrupts, and it has worked surprisingly well so far. I don't know exactly how well it fares performance-wise, and I have some extensions I want to add in the future, but here's the basic idea.
What I've decided to do is turn the edge-triggered interrupt mechanism back into a level-triggered mechanism, like polling. When an interrupt fires, a table entry is marked as 'interrupt fired', and then the interrupt is masked. Threads can then manually reset this table entry, which simultaneously unmasks the interrupt (so that if it were reset without placating the device, the interrupt would fire immediately). At first, this seems stupid, because it seems to turn interrupts back into polling. The key, however, is that control is still passed to the kernel on the rising edge, which means an equally-efficient sleep waiting mechanism can be implemented. Threads can wait on an interrupt: if the interrupt has not fired, the thread sleeps until it has, and if it has fired, the thread returns immediately. This combined with a select()-like call and additional primitives for testing instead of waiting on interrupts makes for a very intuitive interface.
With shared IRQ lines, there are some issues that arise: if two threads wait on one interrupt in this fashion, the one that the interrupt was not intended for will wait again and then be immediately awoken again until the other thread resets the interrupt, causing poor performance. My solution to this is to have each thread keep track of which interrupts it has 'seen' (i.e. waited upon.) If a thread receives a false interrupt, it then waits on the interrupt again, but with flags set so that it does not return unless an unseen interrupt has fired. The reason this cannot be implemented as a third global 'seen' state for the interrupt is subtle and involves an edge case I'm too lazy to describe right now.
What I've decided to do is turn the edge-triggered interrupt mechanism back into a level-triggered mechanism, like polling. When an interrupt fires, a table entry is marked as 'interrupt fired', and then the interrupt is masked. Threads can then manually reset this table entry, which simultaneously unmasks the interrupt (so that if it were reset without placating the device, the interrupt would fire immediately). At first, this seems stupid, because it seems to turn interrupts back into polling. The key, however, is that control is still passed to the kernel on the rising edge, which means an equally-efficient sleep waiting mechanism can be implemented. Threads can wait on an interrupt: if the interrupt has not fired, the thread sleeps until it has, and if it has fired, the thread returns immediately. This combined with a select()-like call and additional primitives for testing instead of waiting on interrupts makes for a very intuitive interface.
With shared IRQ lines, there are some issues that arise: if two threads wait on one interrupt in this fashion, the one that the interrupt was not intended for will wait again and then be immediately awoken again until the other thread resets the interrupt, causing poor performance. My solution to this is to have each thread keep track of which interrupts it has 'seen' (i.e. waited upon.) If a thread receives a false interrupt, it then waits on the interrupt again, but with flags set so that it does not return unless an unseen interrupt has fired. The reason this cannot be implemented as a third global 'seen' state for the interrupt is subtle and involves an edge case I'm too lazy to describe right now.
Re: Abstracting the interrupt interface
Sounds similar to my method of IRQ-thread interaction, except that I have a "flag" in the thread block which the IRQ sets when an interrupt is fired, and which can be waited on by the thread in a race-free manner. OTOH, I use this for other things beside IRQs as well, so it is not tied to that, but it was invented to handle IRQs in a race-free way.
- NickJohnson
- Member
- Posts: 1249
- Joined: Tue Mar 24, 2009 8:11 pm
- Location: Sunnyvale, California
Re: Abstracting the interrupt interface
Interesting. Do interrupts set all of the threads' interrupt bits, or just threads that have waited for that interrupt?
Re: Abstracting the interrupt interface
Actually, in my case, it is a syscall that is called in the interrupt-handler, which takes the thread-id as a parameter, that sets the "signalled" bit in the thread. The thread uses another syscall to wait for the signalled state to become set. That way, the mechanism can be used by interrupt handlers that need it (most use it), and the syscall can also be used for other purposes as well (like to create events and waitable objects).NickJohnson wrote:Interesting. Do interrupts set all of the threads' interrupt bits, or just threads that have waited for that interrupt?