Subject: Re: pmax interrupt problem solved
To: Michael L. Hitch <mhitch@lightning.msu.montana.edu>
From: Todd Whitesel <toddpw@best.com>
List: port-pmax
Date: 03/08/2000 23:04:31
>   I think the code was trying to do the same thing Ultrix does, but missed
> an important case - i.e. when 2 or 3 interrupts were simultaneously
> pending in the same group of bits used in the switch() value.  I
> disassembled the kn02iointr() routine from Ultrix, and although I haven't
> fully analyzed it yet, I think Ultrix includes all possible combinations
> of the interrupt bits used in the switch() value.  It appears to only
> process one interrupt, and would appear to rely on makeing a second (and
> possibly third) pass through kn02iointr() to pick up the rest of the
> pending interrupts.

I haven't seen the code, but speaking from a general interrupt coding
background, the above description makes a lot of sense. Apologies in
advance to the gurus, I'm sure the following is old hat to most of you.

Generally, when you have a wide register full of interrupt pending bits,
the critical path of the interrupt dispatcher is to obtain a bit position
of a set bit (preferably the highest priority such bit). If your interrupt
handlers aren't hogging the machine, which _should_ be the case most of
the time, then usually there will only be one bit set -- just not always.

So you want to optimize for the single bit case, but still behave correctly
in the multiple bit case. If the interrupt logic is all level-sensitive
(and I expect it is), then you can get away with handling one interrupt at
a time; the hardware will just throw you back into the interrupt handler
if there are still any bits set. Plus, this method automatically picks the
highest priority pending interrupt on the next pass. It is false economy to
check every single bit in the interrupt register on every pass through, and
process everything before returning; also, that method is less than fair to
higher priority interrupts which come in just after their bit is checked.

However, this can be short-cutted to save time in the multiple interrupt
case, and the resulting implementation is also good for the case of multiple
interrupts of different priorities occurring back-to-back. Suppose the lower
numbered bits are always higher priority and we have a 16-bit interrupt
pending register; then we end up with something like this:

void dispatch_intr (int level)
{
/* handle interrupt at bit position 'level' and do whatever
   is necessary to clear the pending bit in the hardware. */
}

void dispatch_4_bits (int bits, int levelbase)
{
switch (bits)
	{
	case 1: case 3: case 5: case 7: case 0x9: case 0xB: case 0xD: case 0xF:
		dispatch_intr (levelbase + 0);	break;
	case 2: case 6: case 0xA: case 0xE:
		dispatch_intr (levelbase + 1);	break;
	case 4: case 0xC:
		dispatch_intr (levelbase + 2);	break;
	case 0x8:
		dispatch_intr (levelbase + 3);	break;
	}
}

void dispatch_16_bits_and_loop ()
{
int x;
while (0 != (x = GET_INTERRUPT_PENDING_BITS()))
	{

	if (x & 0xff)
		{
		if (x & 0xf)
			dispatch_4_bits (x, 0);
		else
			dispatch_4_bits (x >> 4, 4);
		}
	else if (x & 0xff00)
		{
		if (x & 0xf00)
			dispatch_4_bits (x >> 8, 8);
		else
			dispatch_4_bits (x >> 12, 0xC);
		}
	}
}

Note that if the priorities are really like this we can push the downshift
into the subroutine; however it's more likely that the priorities are
scattered around, in which case we won't be able to handle every possible
ordering, but we might get close to it by inlining the subroutine and
rearranging the switch-table entries to reflect the mini-priority ordering
within each group of bits. (It probably also makes sense to avoid a switch
entirely, and use two arrays, one of interrupt bits-to-levels and another of
levels-to-FUNCPTRs, to accomplish the same result with straight-line code.)

Note also that we go back and check the pending register again until we've
gone a complete round with nothing in it. This avoids wasting time returning
all the way back to user mode if we're just going to get interrupted again,
and it also automatically finds the next highest-priority interrupt that is
still pending; plus it notices any new ones that show up while we're running!

If so many interrupts show up that we never return, well, we're probably
screwed anyway. With the above code we actually could compile in a check
to see how many times we looped and panic if we think we're stuck here.

One final note: if the compiler doesn't rotate the loop, we can always do
that explicitly in the code. Rotated, the only overhead versus the Ultrix
method is the second "x = GET_INTERRUPT_PENDING_BITS()" and the branch.

Todd Whitesel
toddpw @ best.com