Hardware interrupts

Hardware interrupts are generated by hardware devices when something unusual happens; this could be a keypress or a mouse move or any other action. This is done to minimize CPU time, else the CPU would have to check all installed hardware for data in a big loop (this method is called 'polling') and this would take much time. A standard IBM-PC has two interrupt controllers, that are responsible for these hardware interrupts: both allow up to 8 different interrupt sources (IRQs, interrupt requests). The second controller is connected to the first through IRQ 2 for compatibility reasons, e.g. if controller 1 gets an IRQ 2, he hands the IRQ over to controller 2. Because of this up to 15 different hardware interrupt sources can be handled. IRQ 0 through IRQ 7 are mapped to interrupts 8h to Fh and the second controller (IRQ 8 to 15) is mapped to interrupt 70h to 77h. All of the code and data touched by these handlers MUST be locked (via the various locking functions) to avoid page faults at interrupt time. Because hardware interrupts are called (as in real mode) with interrupts disabled, the handler has to enable them before it returns to normal program execution. Additionally a hardware interrupt must send an EOI (end of interrupt) command to the responsible controller; this is accomplished by sending the value 20h to port 20h (for the first controller) or A0h (for the second controller). The following example shows how to redirect the keyboard interrupt.

Example

{ This example demonstrates how to chain to a hardware interrupt.
In more detail, it hooks the keyboard interrupt, calls a user
procedure which in this case simply turns the PC speaker on and off.
Then the old interrupt is called.
}
{$ASMMODE ATT}
{$MODE FPC}
uses
        crt,
        go32;
const
        { keyboard is IRQ 1 -> interrupt 9 }
        kbdint = $9;
var
        { holds old PM interrupt handler address }
        oldint9_handler : tseginfo;
        { new PM interrupt handler }
        newint9_handler : tseginfo;
        { pointer to interrupt handler }
        clickproc : pointer;
        { the data segment selector }
        backupDS : Word; external name '___v2prt0_ds_alias';
{ interrupt handler }
procedure int9_handler; assembler;
asm
        cli
        { save all registers, because we don't know which the compiler
        uses for the called procedure }
        pushl %ds
        pushl %es
        pushl %fs
        pushl %gs
        pushal
        { set up to call a FPC procedure }
        movw %cs:backupDS, %ax
        movw %ax, %ds
        movw %ax, %es
        movw dosmemselector, %ax
        movw %ax, %fs
        { call user procedure }
        call *clickproc
        { restore all registers }
        popal
        popl %gs
        popl %fs
        popl %es
        popl %ds
        { note: in go32v2 mode %cs=%ds=%es !!!}
        ljmp %cs:oldint9_handler { call old handler }
        { we don't need to do anything more, because the old interrupt
        handler does this for us (send EOI command, iret, sti...) }
end;
{ dummy procedure to retrieve exact length of handler, for locking
and unlocking functions  }
procedure int9_dummy; begin end;
{ demo user procedure, simply clicks on every keypress }
procedure clicker;
begin
        sound(500); delay(10); nosound;
end;
{ dummy procedure to retrieve exact length of user procedure for
locking and unlocking functions }
procedure clicker_dummy; begin end;
{ installs our new handler }
procedure install_click;
begin
        clickproc := @clicker;
        { lock used code and data }
        lock_data(clickproc, sizeof(clickproc));
        lock_data(dosmemselector, sizeof(dosmemselector));
        lock_code(@clicker,
                longint(@clicker_dummy) - longint(@clicker));
        lock_code(@int9_handler,
                longint(@int9_dummy)-longint(@int9_handler));
        { fill in new handler's 48 bit pointer }
        newint9_handler.offset := @int9_handler;
        newint9_handler.segment := get_cs;
        { get old PM interrupt handler }
        get_pm_interrupt(kbdint, oldint9_handler);
        { set the new interrupt handler }
        set_pm_interrupt(kbdint, newint9_handler);
end;
{ deinstalls our interrupt handler }
procedure remove_click;
begin
        { set old handler }
        set_pm_interrupt(kbdint, oldint9_handler);
        { unlock used code & data }
        unlock_data(dosmemselector, sizeof(dosmemselector));
        unlock_data(clickproc, sizeof(clickproc));
        unlock_code(@clicker,
                longint(@clicker_dummy)-longint(@clicker));
        unlock_code(@int9_handler,
                longint(@int9_dummy)-longint(@int9_handler));
end;
var
        ch : char;
begin
        install_click;
        Writeln('Enter any message. Press return when finished');
        while (ch <> #13) do begin
                ch := readkey; write(ch);
        end;
        remove_click;
end.