!************************************************************************** !* !* Network driver interface for netboot bootrom !* !* Module: interrupt.S !* Purpose: Handle interrupt saving/restoring !* Entries: intsave, intrestore, intchanged, intset, intreset, !* intstart, intstop, chkhwint, dohwint, endhwint !* !************************************************************************** !* !* Copyright (C) 1998-2003 Gero Kuhlmann !* !* This program is free software; you can redistribute it and/or modify !* it under the terms of the GNU General Public License as published by !* the Free Software Foundation; either version 2 of the License, or !* any later version. !* !* This program is distributed in the hope that it will be useful, !* but WITHOUT ANY WARRANTY; without even the implied warranty of !* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the !* GNU General Public License for more details. !* !* You should have received a copy of the GNU General Public License !* along with this program; if not, write to the Free Software !* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. !* !* $Id: interrupt.S,v 1.5 2003/01/25 23:29:41 gkminix Exp $ !* ! !************************************************************************** ! ! Load assembler macros and other include files. ! #include #include #include ! !************************************************************************** ! ! Set the way of handling interrupts. See dohwint routine for a further ! explanation. ! #ifndef INTMODE # define INTMODE 1 #endif #ifndef INTCHECK # if INTMODE == 3 # define INTCHECK 1 # else # define INTCHECK 0 # endif #endif #ifndef MASKINT # if INTMODE == 3 # define MASKINT 1 # else /* see comment at dohwint for further explanation */ # define MASKINT 0 # endif #endif ! !************************************************************************** ! ! We have to deal with three types of interrupt vectors: ! ! Type 0 - Vector not used by network driver ! Type 1 - Vector not permanently used by network driver. Such a vector ! is required during network driver initialization and shutdown, ! but not while the network driver is in operation. These include ! the DOS simulator interrupts, a packet driver software interrupt ! (which gets called directly by the packet driver interface in- ! stead of using INT) and the network card hardware interrupt ! (which also gets called directly by the driver interface on ! behalf of the bootrom kernel) ! Type 2 - Vector will be used by network driver permanently. Usually ! there should be no vector of this type, but some network ! drivers require some redirected interrupts for example to ! handle timers etc. ! ! We have to save all vectors changed by the network driver in a seperate ! array. The structure of this array is also defined here. ! INTTAB_TYPE equ $0000 ! offset to interrupt type INTTAB_VECT equ $0001 ! offset to interrupt vector number INTTAB_OLD equ $0002 ! offset to old interrupt vector INTTAB_NEW equ $0006 ! offset to new interrupt vector INTTAB_TEMP equ $000A ! offset to temporary interrupt vector INTTAB_LENGTH equ $000E ! length of this structure INTTAB_NUM equ 32 INTTAB_SIZE equ (INTTAB_LENGTH * INTTAB_NUM) ! !************************************************************************** ! ! Macros to preform certain actions on both PICs simultaneously. We implement ! these as macros for speed. Space shouldnt be a consideration within a ! interrupt service routine. ! ! Macro to generate a 5 microsecond delay between two data accesses to ! a PIC. This delay is required per the EISA specification. ! macro waitpic push ax in al,$61 ! these input instructions will in al,$61 ! generate the necessary delay on in al,$61 ! any system, even the fastest pentiums pop ax mend ! Macro to send a command to both PICs macro sendcmd #ifdef IS386 out MASTER_CMD,al out SLAVE_CMD,al #else test byte ptr [feat + 0],#FB1_SLAVE jz scmd_?0 out SLAVE_CMD,al scmd_?0: out MASTER_CMD,al #endif mend ! Macro to read a data byte from both PICs macro readdata #ifdef IS386 in al,SLAVE_CMD mov ah,al in al,MASTER_CMD #else xor al,al test byte ptr [feat + 0],#FB1_SLAVE jz rdd_?0 in al,SLAVE_CMD rdd_?0: mov ah,al in al,MASTER_CMD #endif mend ! Macro to set the interrupt mask with both PICs macro setimr #ifdef IS386 out MASTER_IMR,al xchg al,ah out SLAVE_IMR,al #else xchg al,ah test byte ptr [feat + 0],#FB1_SLAVE jz simr_?0 out SLAVE_IMR,al simr_?0: xchg al,ah out MASTER_CMD,al #endif mend ! Macro to read the interrupt mask registers from both PICs macro readimr #ifdef IS386 in al,SLAVE_IMR mov ah,al in al,MASTER_IMR #else xor al,al test byte ptr [feat + 0],#FB1_SLAVE jz rimr_?0 in al,SLAVE_IMR rimr_?0: mov ah,al in al,MASTER_IMR #endif mend ! !************************************************************************** ! ! BSS segment: ! .bss .lcomm inttab,INTTAB_SIZE ! changed interrupt table .lcomm hwvect,4 ! network driver hw int vector .lcomm irqmask,2 ! IRQ masks: master(low), slave(high) #if INTMODE == 3 .lcomm oldmask,2 ! old interrupt mask #endif ! !************************************************************************** ! ! Data segment: ! .data ! Data specifying certain system features: #ifndef IS386 model: .byte MODEL_PC ! system model byte feat: .word 0 ! feature bytes 1 and 2 #endif ! The following table specifies all those hardware interrupts, at which a ! network card cant be installed: noint_tab: #ifdef IS386 fdint: .byte HWINT_DISKETTE ! diskette driver hardware interrupt .byte HWINT_HD_AT ! hard disk hardware interrupt math: .byte HWINT_MATHERR ! math coprocessor hardware interrupt mouse: .byte HWINT_MOUSE_PS2 ! PS/2 mouse hardware interrupt .byte HWINT_KBD ! keyboard hardware interrupt .byte HWINT_CLOCK ! CMOS clock hardware interrupt .byte HWINT_TIMER ! timer hardware interrupt .byte $FF ! identifies end of table #else fdint: .byte HWINT_DISKETTE ! diskette driver hardware interrupt hdint: .byte HWINT_HD_XT ! hard disk hardware interrupt math: .byte 0 ! math coprocessor hardware interrupt mouse: .byte 0 ! PS/2 mouse hardware interrupt kbdint: .byte HWINT_KBD ! keyboard hardware interrupt clock: .byte 0 ! CMOS clock hardware interrupt .byte HWINT_TIMER ! timer hardware interrupt .byte $FF ! identifies end of table #endif ! !************************************************************************** ! ! Start code segment. ! .text public intsave ! public entry points public intrestore public intchanged public intstart public intstop public intset public intreset public chkhwint public dohwint public endhwint ! !************************************************************************** ! ! Save interrupt vector table. This is usually called _before_ a network ! driver gets started. ! Input: SS:BP - pointer to buffer for interrupt table (has to have a ! size of at least 1kB) ! Output: none ! Registers changed: CX, SI, DI, ES ! intsave: cld push ds mov cx,ss mov es,cx xor si,si mov di,bp mov ds,si #ifdef IS386 mov cx,#IDT_SIZE / 4 rep movsd #else mov cx,#IDT_SIZE / 2 rep movsw #endif pop ds ret ! !************************************************************************** ! ! Restore all those interrupts which have been changed by the network ! driver and identify those which have been changed. This routine usually ! gets started _after_ a network driver has been started. At the end of ! this routine, the interrupt table looks exactly like it looked before ! the network driver started. ! Input: SS:BP - pointer to buffer of interrupt table ! Output: Carry flag set if error ! Registers changed: (E)AX, BX, CX, DX, SI, DI, ES ! intrestore: xor di,di mov es,di mov si,#inttab ! pointer to local interrupt table xor bx,bx ! interrupt vector counter xor cx,cx ! error counter irsr1: #ifdef IS386 mov eax,[bp + di] ! get next old interrupt vector seg es xchg eax,[di] ! restore old interrupt vector cmp eax,[bp + di] ! check if vector has been changed je irsr3 cmp si,#inttab + INTTAB_SIZE jae irsr2 mov byte ptr [si + INTTAB_TYPE],#1 mov byte ptr [si + INTTAB_VECT],bl mov dword ptr [si + INTTAB_NEW],eax add si,#INTTAB_LENGTH jmp irsr4 irsr2: inc cx ! increase error counter irsr3: xor eax,eax ! vector has not been changed irsr4: mov [bp + di],eax ! save new vector into buffer #else /* IS386 */ mov ax,[bp + di + 0] ! get next old interrupt vector mov dx,[bp + di + 2] cli seg es xchg ax,[di + 0] ! restore old interrupt vector seg es xchg dx,[di + 2] sti cmp ax,[bp + di + 0] ! check if vector has been changed jne irsr5 cmp dx,[bp + di + 2] je irsr3 irsr5: cmp si,#inttab + INTTAB_SIZE jae irsr5 mov byte ptr [si + INTTAB_TYPE],#1 mov byte ptr [si + INTTAB_VECT],bl mov word ptr [si + INTTAB_NEW + 0],ax mov word ptr [si + INTTAB_NEW + 2],dx add si,#INTTAB_LENGTH jmp irsr4 irsr2: inc cx ! increase error counter irsr3: xor ax,ax ! vector has not been changed xor dx,dx irsr4: mov [bp + di + 0],ax ! save new vector into buffer mov [bp + di + 2],dx #endif /* IS386 */ add di,#4 ! go to next vector inc bx cmp bx,#IDT_NUM ! continue with next vector jb irsr1 ! when terminating, carry is reset jcxz irsr9 ! check for error stc irsr9: ret ! !************************************************************************** ! ! After the network driver got started, the driver interface calls ! intrestore above to restore the old interrupt table and save all ! new vectors in the IDT buffer. The driver interface can then identify ! all those vectors in the IDT buffer it wants to handle directly (for ! example by calling checkhwint below), and sets these vectors to 0 ! in the IDT buffer. This routine will then check for those vectors ! which havent been set to 0, and therefore have to remain changed ! during network driver activation, e.g. which are of type 2. It will ! save the new vectors into the local array and set type 2. ! With this routine, the network driver interface also has the chance ! of modifying a new interrupt vector before it gets set by changing ! it in the IDT buffer before calling this interrupt table. ! After executing this routine, the IDT buffer in SS:BP can be released ! from memory as it is no longer needed. ! Input: SS:BP - pointer to buffer of interrupt table ! Output: None ! Registers changed: (E)AX, BX, CX, DX, SI, DI, ES ! intchanged: xor di,di xor bx,bx ! interrupt vector counter ichg1: #ifdef IS386 mov eax,[bp + di] or eax,eax ! check if vector has been changed jz ichg4 mov si,#inttab ! pointer to local interrupt table mov cx,#INTTAB_NUM ichg2: cmp [si + INTTAB_VECT],bl ! find vector in local interrupt table je ichg3 add si,#INTTAB_LENGTH ! proceed with next interrupt loop ichg2 jmp ichg4 ! interrupt not found - dont restore ichg3: mov [si + INTTAB_NEW],eax ! save new interrupt mov byte ptr [si + INTTAB_TYPE],#2 #else /* IS386 */ mov ax,[bp + di + 0] mov dx,[bp + di + 2] mov cx,ax or cx,dx ! check if vector has been changed jz ichg4 mov si,#inttab ! pointer to local interrupt table mov cx,#INTTAB_NUM ichg2: cmp [si + INTTAB_VECT],bl ! find vector in local interrupt table je ichg3 add si,#INTTAB_LENGTH ! proceed with next interrupt loop ichg2 jmp ichg4 ! interrupt not found - dont restore ichg3: mov [si + INTTAB_NEW + 0],ax mov [si + INTTAB_NEW + 2],dx ! save new interrupt mov byte ptr [si + INTTAB_TYPE],#2 #endif /* IS386 */ ichg4: add di,#4 ! go to next vector inc bx cmp bx,#IDT_NUM ! continue with next vector jb ichg1 ! when terminating, carry is reset ret ! !************************************************************************** ! ! When calling START_UNDI, the processor interrupt table is still looks ! the same as when the bootrom started. This call will set all permanently ! used interrupts. ! Input: None ! Output: None ! Registers changed: (E)AX, CX, DX, SI, DI, ES ! intstart: xor di,di mov es,di mov si,#inttab mov cx,#INTTAB_NUM istrt1: cmp byte ptr [si + INTTAB_TYPE],#2 jne istrt2 #ifdef IS386 mov eax,[si + INTTAB_OLD] ! check if vector has already been or eax,eax ! changed jnz istrt2 movzx di,byte ptr [si + INTTAB_VECT] shl di,#2 mov eax,[si + INTTAB_NEW] seg es xchg eax,[di] mov [si + INTTAB_OLD],eax #else /* IS386 */ mov ax,[si + INTTAB_OLD + 0] or ax,[si + INTTAB_OLD + 2] jnz istrt2 mov al,byte ptr [si + INTTAB_VECT] xor ah,ah shl ax,#1 shl ax,#1 mov di,ax mov ax,[si + INTTAB_NEW + 0] mov dx,[si + INTTAB_NEW + 2] cli seg es xchg ax,[di + 0] seg es xchg dx,[di + 2] sti mov [si + INTTAB_OLD + 0],ax mov [si + INTTAB_OLD + 2],dx #endif /* IS386 */ istrt2: add si,#INTTAB_LENGTH loop istrt1 ret ! !************************************************************************** ! ! When STOP_UNDI gets called we have to restore all permanent interrupt ! vectors. ! Input: None ! Output: Carry flag set if interrupt vectors cant get restored ! Registers changed: (E)AX, BX, CX, DX, SI, DI, ES ! intstop: xor bx,bx ! BX is error counter mov es,bx mov si,#inttab mov cx,#INTTAB_NUM istop1: cmp byte ptr [si + INTTAB_TYPE],#2 jne istop4 #ifdef IS386 movzx di,byte ptr [si + INTTAB_VECT] shl di,#2 seg es mov eax,[di] ! check if vector has not been changed cmp eax,[si + INTTAB_NEW] je istop3 inc bx ! increase error counter jmp istop4 istop3: xor eax,eax xchg eax,[si + INTTAB_OLD] ! set old vector to 0 in local array seg es mov [di],eax ! restore old vector #else /* IS386 */ mov al,byte ptr [si + INTTAB_VECT] xor ah,ah shl ax,#1 shl ax,#1 mov di,ax seg es mov ax,[di + 0] ! check if vector has not been changed mov dx,[di + 2] cmp ax,[si + INTTAB_NEW + 0] jne istop2 cmp dx,[si + INTTAB_NEW + 2] je istop3 istop2: inc bx ! increase error counter jmp istop4 istop3: xor ax,ax xor dx,dx ! set old vector to 0 in local array xchg ax,[si + INTTAB_OLD + 0] xchg dx,[si + INTTAB_OLD + 2] cli seg es mov [di + 0],ax ! restore old vector seg es mov [di + 2],dx sti #endif /* IS386 */ istop4: add si,#INTTAB_LENGTH loop istop1 or bx,bx ! set carry flag if error jz istop9 stc istop9: ret ! !************************************************************************** ! ! Before we terminate the network driver, we have to set all interrupt ! vectors to the state they had after the driver started. This is because ! the driver might check if it can restore the vector before actually ! doing it. This routine gets called _before_ a network driver gets ! terminated. ! Input: None ! Output: Carry flag set if network driver cant get removed from memory ! Registers changed: (E)AX, BX, CX, DX, SI, DI, ES ! intset: xor bx,bx mov es,bx mov cx,#INTTAB_NUM mov si,#inttab iset1: cmp byte ptr [si + INTTAB_TYPE],#0 je iset6 #ifdef IS386 movzx di,byte ptr [si + INTTAB_VECT] shl di,#2 mov eax,[si + INTTAB_NEW] cmp byte ptr [si + INTTAB_TYPE],#2 jne iset3 seg es cmp eax,[di] ! check if vector has been changed je iset4 mov dx,word ptr [si + INTTAB_OLD + 0] or dx,word ptr [si + INTTAB_OLD + 2] jz iset3 inc bx iset3: seg es ! set new vector xchg eax,[di] iset4: mov [si + INTTAB_TEMP],eax ! save old vector #else /* IS386 */ mov al,byte ptr [si + INTTAB_VECT] xor ah,ah shl ax,#1 shl ax,#1 mov di,ax mov ax,[si + INTTAB_NEW + 0] mov dx,[si + INTTAB_NEW + 2] cmp byte ptr [si + INTTAB_TYPE],#2 jne iset3 seg es cmp ax,[di + 0] ! check if vector has been changed jne iset2 seg es cmp dx,[di + 2] je iset4 iset2: push dx mov dx,word ptr [si + INTTAB_OLD + 0] or dx,word ptr [si + INTTAB_OLD + 2] pop dx jz iset3 inc bx iset3: cli seg es ! set new vector xchg ax,[di + 0] seg es xchg dx,[di + 2] sti iset4: mov [si + INTTAB_TEMP + 0],ax mov [si + INTTAB_TEMP + 2],dx #endif /* IS386 */ iset6: add si,#INTTAB_LENGTH ! continue with next table entry loop iset1 or bx,bx jz iset9 stc iset9: ret ! !************************************************************************** ! ! Reset all changed interrupts to the state they had before the network ! driver got terminated. This routine usually gets called _after_ a ! network driver has been terminated. After this routine has been ! executed, the network driver cant get restarted any more. ! Input: None ! Output: None ! Registers changed: (E)AX, CX, DX, SI, DI, ES ! intreset: xor di,di mov es,di mov cx,#INTTAB_NUM mov si,#inttab irst1: cmp byte ptr [si + INTTAB_TYPE],#0 je irst4 #ifdef IS386 movzx di,byte ptr [si + INTTAB_VECT] ! get vector shl di,#2 mov eax,[si + INTTAB_TEMP] cmp byte ptr [si + INTTAB_TYPE],#2 ! if the vector has been jne irst3 ! changed for mode 2 we cant cmp eax,[si + INTTAB_NEW] ! restore the old vector but jne irst3 ! have to use the temp vector mov dx,word ptr [si + INTTAB_OLD + 0] or dx,word ptr [si + INTTAB_OLD + 2] jz irst3 mov byte ptr [si + INTTAB_TYPE],#0 mov eax,[si + INTTAB_OLD] irst3: seg es ! restore old vector mov [di],eax #else /* IS386 */ mov al,byte ptr [si + INTTAB_VECT] ! get vector xor ah,ah shl ax,#1 shl ax,#1 mov di,ax mov ax,[si + INTTAB_TEMP + 0] mov dx,[si + INTTAB_TEMP + 2] cmp byte ptr [si + INTTAB_TYPE],#2 ! if the vector has been jne irst3 ! changed for mode 2 we cant cmp ax,[si + INTTAB_NEW + 0] ! restore the old vector but jne irst2 ! have to use the temp vector cmp dx,[si + INTTAB_NEW + 2] je irst3 irst2: push dx mov dx,word ptr [si + INTTAB_OLD + 0] or dx,word ptr [si + INTTAB_OLD + 2] pop dx jz irst3 mov byte ptr [si + INTTAB_TYPE],#0 mov ax,[si + INTTAB_OLD + 0] mov dx,[si + INTTAB_OLD + 2] irst3: cli seg es mov [di + 0],ax ! restore old vector seg es mov [di + 2],dx sti #endif /* IS386 */ irst4: add si,#INTTAB_LENGTH loop irst1 ret ! !************************************************************************** ! ! Identify all interrupts redirected by the network driver and find the ! vector which handles hardware interrupts from the network card. This ! usually gets called _after_ calling intrestore, because it requires ! all unchanged interrupt vectors in the buffer to be set to 0. ! Input: SS:BP - pointer to buffer of interrupt table ! Output: CX - hardware interrupt IRQ number (zero if not found) ! DI - offset to vector in interrupt table buffer ! Registers changed: (E)AX, BX, CX, DX, SI, DI ! chkhwint: ! First we have to determine the interrupt vector numbers for certain ! hardware interrupts. Some hardware interrupt sources are on the mainboard ! and cant be used by an add-on card. However, some sources are removable ! or depend upon the type of system used. We need those interrupt numbers ! to rule them out lateron when checking for the network card hardware ! interrupt vector. #ifdef IS386 ! For 386+ systems it gets pretty easy, as they always have a common set ! of components like a slave PIC etc. int INT_EQUIP ! get equipment flags test ax,#$0001 ! check for installed floppy drives jnz chkhwK mov byte ptr [fdint],#0 ! dont have a floppy drive chkhwK: test ax,#$0002 ! check if we have a math coprocessor jnz chkhwL mov byte ptr [math],#0 ! dont have a math coprocessor chkhwL: test ax,#$0004 ! check for PS/2 mouse port jnz chkhw0 mov byte ptr [mouse],#0 ! dont have a PS/2 mouse port #else /* IS386 */ ! If we also have to support older systems than 386, we have to check for ! different BIOS information resources. mov ax,#BIOS_SEG ! 386+ should always support int 15/C0 mov es,ax ! determine the system model seg es mov al,byte ptr [BIOS_MODEL] mov [model],al cmp al,#MODEL_AT ! some older ATs dont support int 15/C0 je chkhwK cmp al,#MODEL_PS2 jne chkhwL ! 286+ systems always have a slave PIC chkhwK: mov byte ptr [feat + 0],#FB1_SLAVE chkhwL: stc mov ah,#$C0 int INT_MISC ! try to get system information from jc chkhwM ! the BIOS, if supported seg es mov ax,[bx + ROMCFG_SIZE] ! check if we got the feature bytes we cmp ax,#ROMCFG_MINLEN ! need jb chkhwM seg es mov al,[bx + ROMCFG_MODEL] mov [model],al ! get real model ID seg es mov ax,[bx + ROMCFG_FB1] mov [feat],ax ! get feature bytes 1 and 2 ! Now determine which interrupts might have been preset according to the ! system information just determined. chkhwM: int INT_EQUIP ! get equipment flags mov bl,byte ptr [model] ! get model ID mov bh,byte ptr [feat + 0] ! get feature byte 1 test ax,#$0001 ! check for installed floppy drives jnz chkhwN mov byte ptr [fdint],#0 ! dont have a floppy drive chkhwN: cmp bl,#MODEL_AT ! only 286+ systems with a slave PIC je chkhwR ! can have slave PIC interrupts cmp bl,#MODEL_PS2 jne chkhwQ chkhwR: test bh,#FB1_SLAVE jz chkhwQ mov byte ptr [hdint],#HWINT_HD_AT test ax,#$0004 ! check for PS/2 mouse port jz chkhwO mov byte ptr [mouse],#HWINT_MOUSE_PS2 chkhwO: test ax,#$0002 ! check if we have a math coprocessor jnz chkhwP mov byte ptr [math],#HWINT_MATHERR chkhwP: test bh,#FB1_RTC jnz chkhwQ ! check if we have a real-time clock mov byte ptr [clock],#HWINT_CLOCK chkhwQ: test byte ptr [feat + 1],#FB2_KBD jz chkhw0 ! no keyboard interrupt if 8042 not mov byte ptr [kbdint],#0 ! installed #endif /* IS386 */ ! We can now scan through the IDT buffer to find the network driver hardware ! interrupt. chkhw0: cld xor di,di xor cx,cx chkhw1: cmp cx,#MIN_MASTER_HWINT ! check if current interrupt vector is jb chkhw2 ! a hardware interrupt on the master PIC cmp cx,#MAX_MASTER_HWINT jbe chkhw3 chkhw2: cmp cx,#MIN_SLAVE_HWINT jb chkhw5 ! check if current interrupt vector cmp cx,#MAX_SLAVE_HWINT ! is a hardware interrupt on the slave ja chkhw5 ! PIC chkhw3: #ifdef IS386 mov eax,[bp + di] or eax,eax ! check if interrupt has been changed #else mov ax,[bp + di + 0] or ax,[bp + di + 2] ! check if interrupt has been changed #endif jz chkhw5 ! A network driver might restore more hardware interrupts than just the one ! used by the network card. Most commonly this other interrupt vector will be ! the one for the hardware timer. However, we have to rule out all predefined ! hardware interrupts, as those cant be used for the network card itself. chkhw4: mov si,#noint_tab ! check no-interrupt table chkhw6: lodsb cmp al,cl ! check if current interrupt is a je chkhw5 ! reserved vector cmp al,#$FF ! check if at end of table jne chkhw6 jmp chkhw7 ! we found the network card interrupt chkhw5: add di,#4 inc cx ! continue with next interrupt cmp cx,#IDT_NUM jb chkhw1 jmp chkhwB ! If we found a hardware interrupt, save it and set its vector in the IDT ! buffer to 0. Special handling is required for interrupt 2, which is ! actually on the slave PIC on AT and higher systems. chkhw7: #ifndef IS386 test byte ptr [feat + 0],#FB1_SLAVE jz chkhwD #endif cmp cx,#SLAVE_IRQ_NUM ! use interrupt number 9 instead of 2 jne chkhwD mov cx,#SLAVE_IRQ_REDIR chkhwD: mov ax,cx sub cx,#MIN_MASTER_HWINT ! compute IRQ number jc chkhwA cmp cx,#PIC_IRQ_NUM ! check if IRQ is on master PIC jb chkhwC chkhwA: mov cx,ax sub cx,#MIN_SLAVE_HWINT jc chkhwB cmp cx,#PIC_IRQ_NUM ! check if IRQ is on slave PIC jae chkhwB add cx,#PIC_IRQ_NUM ! adjust IRQ number for slave PIC jmp chkhwC chkhwB: xor cx,cx ! oops, should never happen jmp chkhw9 chkhwC: mov ax,#1 ! compute IRQ mask shl ax,cl mov [irqmask],ax #ifdef IS386 xor eax,eax ! set vector to 0 in IDT buffer xchg eax,[bp + di] ! save hardware interrupt vector mov [hwvect],eax #else xor ax,ax xchg ax,[bp + di + 0] ! save hardware interrupt vector mov word ptr [hwvect + 0],ax xor ax,ax xchg ax,[bp + di + 2] mov word ptr [hwvect + 2],ax #endif chkhw9: ret ! !************************************************************************** ! ! Actually process a hardware interrupt. This routine assumes that DS ! points to our own data segment! ! Input: none ! Output: Carry flag set if this is not our interrupt. ! Registers changed: AX, BX ! Interrupt flag NOT changed ! ! According to the UNDI specification, the kernel or protocol driver handles ! the hardware interrupt of the network card initially. It will then call ! UNDI to check if this interrupt really came from the network card. Upon ! return, it will then send an EOI to the PICs and calls UNDI again to finally ! process the interrupt. On the first call, UNDI has to disable the network ! card interrupts (which will be done in this routine). Lateron, with the ! second call, UNDI has to reenable the network card interrupt. ! ! The problem here is that with precompiled network drivers (like packet ! drivers or NDIS drivers) the EOI will be sent to the PICs by the driver ! itself. Therefore, the kernel or protocol driver will issue a second EOI ! upon return from this call. Normally, when this routine gets executed ! no other ISR bit is set but ours, which will get reset by the driver EOI. ! Therefore the kernel EOI remains without effect. This is because in a normal ! system, any interrupt service routine should send an EOI before enabling ! interrupts at the processor level which means that low-priority interrupts ! can get interrupted by higher level interrupts only after EOI. When the ! higher-priority interrupt executes, no lower-level ISR bit remains set. ! There is just one possible situation where this double-EOI can cause ! problems. In this situation the following conditions have to apply: ! ! 1.) An interrupt service routine with lower priority than our ! network interrupt gets called and enables interrupts at the ! processor level _before_ sending its own EOI. This really is ! a serious software bug, especially since there exist 8259- ! clones (especially within C&T chipsets) which incorrectly handle ! a non-specific EOI by resetting all ISR bits instead of just ! the one with the highest priority. ! 2.) The network card produces an interrupt in the time between ! enabling interrupts and the lower-priority interrupt service ! routine sending its EOI. ! 3.) The kernel or protocol driver uses a non-specific EOI instead ! of a specific EOI. The network card ISR bit has already been ! reset by the network card driver, so the highest priority ISR ! bit seen by the PIC when it receives a non-specific EOI is that ! of the interrupt with the lower-than-ours priority. ! ! There are three solutions for this problem: ! ! 1.) We simply ignore it and assume that all hardware interrupt ! handlers are written correctly. This should be pretty safe, ! but if you have buggy software and the kernel or protocol driver ! uses non-specific EOI, you might experience occasional hangups. ! 2.) We check that we are the only routine with an ISR bit set (which ! should always be true as long as all interrupt service routines ! are written correctly). If thats not true, we have to ignore the ! network card interrupt. As with option 1 this should be pretty ! safe, but with buggy software we are going to loose network ! packets - independent of the kernel using specific or non-specific ! EOI. At least it doesnt hang the system. ! 3.) We mask out all interrupts with a set ISR bit and then enter ! special mask mode on both PICs. When the kernel or protocol ! driver sends a non-specific EOI the PIC will not be able to ! find a set ISR bit. In special mask mode, masked ISR bits are ! invisible to the PIC according to the Intel 8259A data sheet. ! Unfortunately we dont know if all higher level interrupts can ! safely deal with the special mask mode. Also this would not ! prevent the EOI to reset the ISR bits on buggy chips like with ! those C&T chipsets. ! ! All three solutions have their individual draw-backs. I think option 1 ! will be the safest (as at least our own network kernel will use specific ! EOI), so we make it the default. ! ! Another problem is the UNDI requirement of disabling the interrupt line of ! the network card. This can done at two positions: directly at the network ! cards chip set, or by masking the interrupt at the PIC. The first case is ! not suitable for us because only the network driver (e.g. packet or NDIS ! driver) has access to the network card chipset. However, the second case ! is unsafe when using a PCI network card, because the PCI bus allows shared ! interrupts. If another card uses the same interrupt as the network card, ! it wont receive its own interrupt while it has been masked out at the PIC. ! Unfortunately, we have to live with this situation, since masking the ! interrupt seems to be necessary. This is because otherwise there is a ! race condition which can cause dropped interrupts: when the interrupt ! service routine of the network driver returns, the interrupt has been ! acknowledged at the PIC already. But the PXE specification calls for ! that acknowledgment to occur at the end of the PXE ISR call. Therefore, ! if interrupts are enabled upon return from the interrupt service call, ! another interrupt can occur until the PXE base code re-acknowledges the ! old interrupt. This will remove the pending new request at the PIC level, ! causing the interrupt to get lost. This is especially important with ! network cards which produce a large amount of interrupts (like transmit ! in addition to receive interrupts). Therefore, we have to live with this ! kludge of masking off interrupts at the PIC level. Note that interrupt ! handling mode 3 always requires interrupt masking enabled. Masking the ! interrupt will be compiled in depending on the MASKINT preprocessor define. ! ! Of course this routine has to be reentrant, as it is called as a part of ! the network card interrupt service routine. ! dohwint: pushf cli #if INTCHECK == 1 mov al,#CMD_READ_ISR sendcmd ! send ISR read command waitpic ! wait for safety readdata ! get ISR from both PICs # if INTMODE == 2 # ifndef IS386 test byte ptr [feat + 0],#FB1_SLAVE jz dohwi2 # endif and al,#~SLAVE_IRQ_MASK dohwi2: cmp ax,[irqmask] ! check if we are the only interrupt je dohwi1 ! service routine currently running # else test ax,[irqmask] ! check if our routine has been jnz dohwi1 ! called due to a hardware interrupt # endif popf stc ret #endif dohwi1: call far ptr [hwvect] ! call network driver handler #if MASKINT == 1 pushf cli # if INTMODE == 3 mov bx,ax ! mask all active interrupts # ifndef IS386 test byte ptr [feat + 0],#FB1_SLAVE jz dohwi3 # endif and bl,#~SLAVE_IRQ_MASK dohwi3: readimr ! read interrupt mask or ax,bx ! set bits for interrupts to be masked waitpic ! wait for safety setimr ! set interrupt mask mov [oldmask],bx ! save old interrupt mask waitpic ! wait for safety mov al,#CMD_SET_SMM sendcmd ! set special mask mode # else readimr ! read interrupt mask or ax,[irqmask] ! set bits for interrupts to be masked waitpic ! wait for safety setimr ! set interrupt mask # endif popf #endif clc ! return without error ret ! !************************************************************************** ! ! Terminate a hardware interrupt. This will enable our hardware interrupt ! (if it has been disabled) and eventually terminate special mask mode. ! This routine assumes that DS points to our own data segment! ! Input: none ! Output: none ! Registers changed: AX, BX ! Interrupt flag NOT changed ! endhwint: #if MASKINT == 1 pushf cli # if INTMODE == 3 mov al,#CMD_RST_SMM sendcmd ! reset special mask mode waitpic ! wait for safety readimr ! read interrupt mask mov bx,[oldmask] # else readimr ! read interrupt mask mov bx,[irqmask] # endif not bx ! unmask all interrupts previously and ax,bx ! masked waitpic ! wait for safety setimr ! set interrupt mask popf #endif ret ! !************************************************************************** ! end