Distinguishing IRQ from CPU exception without PIC remap

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
ar5007eg
Posts: 13
Joined: Wed Feb 20, 2013 12:25 pm

Distinguishing IRQ from CPU exception without PIC remap

Post by ar5007eg »

The wiki says that
In protected mode, the IRQs 0 to 7 conflict with the CPU exception which are reserved by Intel up until 0x1F. (It was an IBM design mistake.) Consequently it is difficult to tell the difference between an IRQ or an software error.
So, I'm just curious on how can one tell the difference between an IRQ or an software error. If it's not too difficult, maybe it's a viable choice if you're constantly switching back to real mode (since in that case you must remap PIC back to it's original setup)?
Mikemk
Member
Member
Posts: 409
Joined: Sat Oct 22, 2011 12:27 pm

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by Mikemk »

Depending on what hardware device it's for, you can try reading the status bit
Programming is 80% Math, 20% Grammar, and 10% Creativity <--- Do not make fun of my joke!
If you're new, check this out.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by Brendan »

Hi,
ar5007eg wrote:The wiki says that
In protected mode, the IRQs 0 to 7 conflict with the CPU exception which are reserved by Intel up until 0x1F. (It was an IBM design mistake.) Consequently it is difficult to tell the difference between an IRQ or an software error.
So, I'm just curious on how can one tell the difference between an IRQ or an software error. If it's not too difficult, maybe it's a viable choice if you're constantly switching back to real mode (since in that case you must remap PIC back to it's original setup)?
Method 1 - Ask The PIC Chip

The PIC chip has an "In Service Register" that keeps track of which IRQs it has sent to the CPU. For example:

Code: Select all

    mov ecx,[esp+8]       ;ecx = interrupt number (must be 0x08 to 0x0F)
    sub cl,7	           ;cl = potential IRQ number + 1
    mov al,0x0B           ;al = "read ISR" command
    out 0x20,al           ;Send "read ISR" command
    in al,0xA0            ;Dummy read (just a small delay)
    in al,0x20            ;al = ISR for master PIC
    shr al,cl             ;carry flag = set if IRQ is in service
However; if an exception occurs in an IRQ handler then you'd read the In Service Register and see that the IRQ is in service and think the exception is an IRQ when it isn't. To avoid that you could have your own variable to keep track of which IRQ handlers have been started and check that in addition to the PIC's In Service Register. Basically:

Code: Select all

    if( IRQ_handler_was_started[ IRQ_number] ) goto must_be_exception;
    if( PIC_says_IRQ_is_not_in_service ) goto must_be_exception;
    /* Must be an IRQ */
    IRQ_handler_was_started[ IRQ_number] = true;   // Set to "false" when you send EOI!
    goto must_be_IRQ;
Method 2 - Use Different IVT and Remap The Master PIC

In real mode, you can change the address of the IVT to somewhere else (e.g. using the "LIDT" instruction). This means that you can reconfigure the master PIC chip so it generates different interrupts; where your interrupt handlers would pass control to the BIOS's original interrupt handlers. For example, you could configure the master PIC so that IRQs 8 to 15 generate interrupts 0xF0 to 0xF7, and when interrupt 0xF3 occurs you can pass control to the BIOS's IRQ handler (e.g. "jmp far [BIOS_IVT+(0x08+0x03)*4]").

Note that in protected mode you need to have your own IDT anyway (otherwise the CPU will crash when an interrupt/exception occurs because it's thinks a pair of 4-byte IVT entries are an 8-byte IDT entry).


Note

You should only use the BIOS during boot (e.g. in boot loaders designed for "PC BIOS") and should not use the BIOS after boot. This means that the 2 methods above may be overkill - most people just assume/hope that their boot code won't cause any exceptions; then remap the PIC chips after they've stopped using the BIOS.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
ar5007eg
Posts: 13
Joined: Wed Feb 20, 2013 12:25 pm

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by ar5007eg »

Oh nice, Method 2 seams to be easy. Just to see if I get things straight, I could just make a (4-byte element) vector V, such that V = BIOS_IVT for every i not between 0xF0 and 0xF3, and make
V = BIOS_IVT[0x08 + i - 0xF0] for every i between 0xF0 and 0xF3; and then change IVT to V and make the PIC remap. So this solves the problem of having to mess with PIC every time one tries to go back to real mode.

Now, one other thing (maybe loosely related) is: what if I wanted to run BIOS interrupts from protected mode but without using v86 (just as an exercise)? I thought about the following solution:
- Create a [ring 1] 16-bit task
- Make a LDT, mapping segment number X to a segment which has start address (X * 0x10)
- Unfortunately it's not possible to map every 16-bit segment number X, since LDT is constrained to 8k entries, so just choose at random 8k segment numbers and put the associated map into LDT
- If you get a "Segment Not Present" it's because there's a segment number Y not mapped inside LDT; Put this Y associated map inside LDT (overwriting some other random map)

Then, what would go wrong if one tries to execute [real-mode code associated to some BIOS interrupt] within this task? What if this code itself has software interrupt instructions, would I be able to distinguish them from protected mode CPU exceptions?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by Brendan »

Hi,
ar5007eg wrote:Oh nice, Method 2 seams to be easy. Just to see if I get things straight, I could just make a (4-byte element) vector V, such that V = BIOS_IVT for every i not between 0xF0 and 0xF3, and make
V = BIOS_IVT[0x08 + i - 0xF0] for every i between 0xF0 and 0xF3; and then change IVT to V and make the PIC remap. So this solves the problem of having to mess with PIC every time one tries to go back to real mode.


That should work (as long as BIOS doesn't change any of its IVT entries). You'd only have to install your own exception handlers.

There is one other problem you may not have realised yet: interrupts 0x10 to 0x1F are used for software interrupts and are also exceptions. For example, there's no easy way to tell the difference between "Machine check exception" and BIOS keyboard services, no easy way to tell the difference between "SIMD floating point exception" and BIOS disk services, etc.

ar5007eg wrote:Now, one other thing (maybe loosely related) is: what if I wanted to run BIOS interrupts from protected mode but without using v86 (just as an exercise)? I thought about the following solution:
- Create a [ring 1] 16-bit task
- Make a LDT, mapping segment number X to a segment which has start address (X * 0x10)
- Unfortunately it's not possible to map every 16-bit segment number X, since LDT is constrained to 8k entries, so just choose at random 8k segment numbers and put the associated map into LDT
- If you get a "Segment Not Present" it's because there's a segment number Y not mapped inside LDT; Put this Y associated map inside LDT (overwriting some other random map)


This can't work reliably. For a start, most BIOS functions will probably attempt to load 0x0000 into a segment register to access the BIOS Data Area, but in (16-bit) protected mode 0x0000 is the NULL segment so every time the BIOS tries to access data in its BIOS Data Area it's going to cause a general protection fault.

You can have protected mode interrupt handlers that switch to real mode, execute the real mode interrupt handler, then switch back to protected mode. In my opinion, this is easier than using virtual8086 mode.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
ar5007eg
Posts: 13
Joined: Wed Feb 20, 2013 12:25 pm

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by ar5007eg »

There is one other problem you may not have realised yet: interrupts 0x10 to 0x1F are used for software interrupts and are also exceptions.
But is this really a problem? I mean, I'm assuming no protected mode code will try to execute "int 13h" (so, IDT[13h] = SIMD_exception_handler) and a "SIMD floating pointer exception" won't happen in real mode, so IVT[13h] = BIOS_disk_service_handler.
most BIOS functions will probably attempt to load 0x0000 into a segment register to access the BIOS Data Area, but in (16-bit) protected mode 0x0000 is the NULL segment
Ha, I see. What a shame!
You can have protected mode interrupt handlers that switch to real mode, execute the real mode interrupt handler, then switch back to protected mode. In my opinion, this is easier than using virtual8086 mode.
The only drawback would be that while in real mode, the system would be frozen (i.e. - no task switching), but maybe that's fine if it's some quick real mode job like reading only few bytes from disk, I assume.
Cheers,
Brendan
Anyway, thank you (very much!) for your attention answering my doubts.
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by Brendan »

Hi,
ar5007eg wrote:
There is one other problem you may not have realised yet: interrupts 0x10 to 0x1F are used for software interrupts and are also exceptions.
But is this really a problem? I mean, I'm assuming no protected mode code will try to execute "int 13h" (so, IDT[13h] = SIMD_exception_handler) and a "SIMD floating pointer exception" won't happen in real mode, so IVT[13h] = BIOS_disk_service_handler.
If you've got protected mode IRQ handlers that switch to real mode, execute the BIOS interrupt, then switch back to protected mode; then it's easy to do the same for software interrupts so that you can do "int 0x13" in protected mode (and let your interrupt handler worry about switching to/from real mode for you). You're right though - you don't have to support that.
ar5007eg wrote:
You can have protected mode interrupt handlers that switch to real mode, execute the real mode interrupt handler, then switch back to protected mode. In my opinion, this is easier than using virtual8086 mode.
The only drawback would be that while in real mode, the system would be frozen (i.e. - no task switching), but maybe that's fine if it's some quick real mode job like reading only few bytes from disk, I assume.
Early during boot you don't do task switching and normally you can't do much while you're waiting for the hardware to complete things like disk IO; so there aren't any real drawbacks.

Later during boot you reach a point where the BIOS has done its job and you discard it - the BIOS Data Area becomes free RAM, you start the other CPUs, you disable the PIC chips and initialise the IO APIC, you setup HPET or local APIC timers and kill the PIT chip, etc.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
azblue
Member
Member
Posts: 147
Joined: Sat Feb 27, 2010 8:55 pm

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by azblue »

Brendan wrote:Hi,

This can't work reliably. For a start, most BIOS functions will probably attempt to load 0x0000 into a segment register to access the BIOS Data Area, but in (16-bit) protected mode 0x0000 is the NULL segment so every time the BIOS tries to access data in its BIOS Data Area it's going to cause a general protection fault.
Couldn't the GPF handler "catch" this and load a valid segment that starts at 0x0000?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by Brendan »

Hi,
azblue wrote:
Brendan wrote:This can't work reliably. For a start, most BIOS functions will probably attempt to load 0x0000 into a segment register to access the BIOS Data Area, but in (16-bit) protected mode 0x0000 is the NULL segment so every time the BIOS tries to access data in its BIOS Data Area it's going to cause a general protection fault.
Couldn't the GPF handler "catch" this and load a valid segment that starts at 0x0000?
You could decode the instruction, figure out which segment register is being loaded, and then set it to a non-zero value that has "base = 0". That just creates another problem: if the BIOS needs to convert it into an actual physical address (e.g. for setting up a DMA transfer or something) the typical "address = segment << 4 + offset" calculation becomes broken because the segment register doesn't contain zero.

To work around that problem, your GPF handler could enable "single step" so that the CPU will only execute one instruction and trigger a debug exception. Then, in the debug exception handler you could set the original segment back to 0x0000 (so that any "address = segment << 4 + offset" calculation continues to work correctly).

Of course that just creates another problem. If the code does something like "mov es,[es:0x1234]" then the segment register has already been overwritten before the debug exception handler "fixes" it. To work-around the problems of the work-around for the original work-around, you could improve the general protection fault handler's "decode the instruction that caused the fault" code so that in some cases it doesn't enable "single step".

But; that still doesn't quite work for some cases, like if SS is meant to be set to zero and the code does "push ss". I guess the general protection fault handler could detect that and emulate the instruction in software (you're already decoding half the instruction set in the general protection fault handler so a little emulation won't hurt too much).

Worse would be if CS is meant to be 0x0000 (e.g. "call far foo" or "push cs") - I can't think of a work-around for that. Fortunately it's unlikely that the BIOS will set CS to zero. More likely would be a BIOS trying to load something like 0xFF07 into CS; but that's the beginning of a whole different set of problems.

It's just not worth the hassle - switch back to real mode or use virtual8086 mode so that you've at least got some chance at reliability.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
ar5007eg
Posts: 13
Joined: Wed Feb 20, 2013 12:25 pm

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by ar5007eg »

Actually, now I realize that, even if we ignore the whole NULL segment problem for a while, we missed (I guess) one major problem with that 'approach': it would only work for segment numbers multiples of 8; I mean, if the real mode code access some memory with CS=9 it would still use the segment that has base address 8*10 = 80 --- since the lower three bits of the selector is used to specify the RPL and whether it's a GDT or an LDT descriptor.

Sorry to abuse, but I kind of fail to understand the purpose of v86. It's harder to set it (compared to just switching to real mode) and I thought the biggest advantage was that it would run in its own Task inside protected mode, allowing the system to have other tasks been switched and executed "while" the real mode code is also executed. But the wiki says it's not a good idea to use int 13h within v86 mode, since it freezes everything while the disk access is being done. Why? While int 13h is executing, wouldn't it get interrupted by the timer interrupt, that could then switch to another task?
User avatar
bluemoon
Member
Member
Posts: 1761
Joined: Wed Dec 01, 2010 3:41 am
Location: Hong Kong

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by bluemoon »

ar5007eg wrote:Sorry to abuse, but I kind of fail to understand the purpose of v86. It's harder to set it (compared to just switching to real mode)
For a more feature rich OS, switching back to real mode requires "stop the world" and perhaps undo all devices configuration - which can be quite far more complex than implementing v86 mode. Today the purpose of v86 mode is limited as it is not supported directly under long mode, and modern OS simply runs an emulator for legacy compatibility (e.g. DOSBOX)
User avatar
JAAman
Member
Member
Posts: 879
Joined: Wed Oct 27, 2004 11:00 pm
Location: WA

Re: Distinguishing IRQ from CPU exception without PIC remap

Post by JAAman »

ar5007eg wrote: Sorry to abuse, but I kind of fail to understand the purpose of v86. It's harder to set it (compared to just switching to real mode) and I thought the biggest advantage was that it would run in its own Task inside protected mode, allowing the system to have other tasks been switched and executed "while" the real mode code is also executed. But the wiki says it's not a good idea to use int 13h within v86 mode, since it freezes everything while the disk access is being done. Why? While int 13h is executing, wouldn't it get interrupted by the timer interrupt, that could then switch to another task?
yes, that is its purpose, and works quite well for applications written for RMode running under a PMode OS -- but it was never intended for drivers or especially BIOS

drivers shouldn't use RMode or VMode for very good reasons, but what your talking about here is BIOS drivers -- which are written very very poorly -- yes the timer interrupt will fire, but the BIOS will still expect to have control of the system until the operation is complete, and thus will break if you take control away (besides, that is still significantly slower than simply sending a command to the drive and then switching to the next process)

in other words, the problem is not with VMode, since it does work like that quite well, but the problem is with the BIOS drivers not being written properly to handle this situation
Post Reply