Configuring Interrupts in Protected Mode (x86)

What is the process for setting interrupts for protected mode?

This link says:

  • Make space for interrupt descriptor table
  • Tell the CPU where this space is (see the GDT tutorial: lidt works just like lgdt)
  • Tell PIC that you no longer want to use the default BIOS settings (see PIC Chip Programming).
  • Write a pair of ISR handlers (see "Interrupt Maintenance Procedures") for IRQs and Exceptions
  • Put the addresses of the ISR handlers in the appropriate descriptors
  • Enable All Supported IRQ Mask Interrupts (PIC)

The third step does not make sense to me (I looked at this link, but nothing was said about the PIC), so I ignored it and completed the next two steps, only to trick it again when I reached the final step. However, from my understanding of interrupts, both of the steps that I did not understand relate to hardware interrupts from the PIC controller and should not affect the interrupt caused by the PIT on IRQ 0. Therefore, I ignored this step.

When I ran my code, it compiled fine and even ran on a virtual machine, but the interrupt seemed to work only once. Then I realized that I was not sending the EOI to the PIC, preventing it from interrupting anymore. However, adding mov al, 0x20 and out 0x20, al immediately before the iret command causes the virtual machine to crash.

Here is my IDT:

 ; idt idt_start : dw 0x00 ; The interrupt handler is located at absolute address 0x00 dw CODE_SEG ; CODE_SEG points to the GDT entry for code db 0x0 ; The unused byte db 0b11101001 ; 1110 Defines a 32 bit Interrupt gate, 0 is mandatory, privilege level = 0 (0b00), the last bit is one so that the CPU knows that the interrupt will be used dw 0x00 ; The higher part of the offset (0x00) is 0x00 idt_end: idt_descriptor : dw idt_end - idt_start - 1 ; Size of our idt, always one less than the actual size dd idt_start ; Start address of our idt 

Here is my interrupt handler (located at absolute location 0x00 in memory):

 ISR_0: push eax add [0x300], byte mov al, 0x20 out 0x20, al pop eax iret times 512-($-$$) db 0 

This is the code I use to enter protected mode and load GDT and IDT into memory:

 [bits 16] switch_to_pm: cli lgdt [gdt_descriptor] lidt [idt_descriptor] mov eax, cr0 or eax, 1 mov cr0,eax jmp CODE_SEG:init_pm [bits 32] init_pm : mov ax, DATA_SEG mov ds, ax mov ss, ax mov es, ax mov fs, ax mov gs, ax mov ebp, 0x90000 mov esp, ebp sti call BEGIN_PM 

My main function (which checks the value 0x300) is as follows:

 void main() { char iii[15]; int * aa = (int *)0x300; for (;;) { setCursor(0, 0); print(itoab(*aa, iii)); } } 

By the way, I checked the use of a memory dump, that everything loads at the correct address, and everything is exactly where it was expected. For example, 0x300 is the free part of memory used just to simplify my code.

+5
source share
1 answer

Let's see how some relatively small core does it, i.e. Linux 0.01 !

  • Make space for interrupt descriptor table

This is done twice (well, technically only once): firstly, the bootloader (path /boot/boot.s boot / /boot/boot.s ) initializes the IDTR, so the CPU is happy when it /boot/boot.s protected mode. The content of the IDTR is as follows:

 idt_48: .word 0 | idt limit=0 .word 0,0 | idt base=0L 

IDTR is loaded as follows:

 lidt idt_48 | load idt with 0,0 

Now the jump can be completed.
Please note that there is no IDT. This is just a dummy, so no error occurs in the kernel.

Then, the real IDT is initialized (path / /boot/head.s ). The space is allocated as follows:

 _idt: .fill 256,8,0 # idt is uninitialized 
  • Tell the CPU where this space is (see GDT lidt : lidt works just like lgdt )

lidt expects a linear address containing the contents of the IDTR. This content is as follows:

 idt_descr: .word 256*8-1 # idt contains 256 entries .long _idt 

IDTR is initialized as follows:

 lidt idt_descr 
  • Tell PIC that you no longer want to use the default BIOS settings (see PIC Chip Programming).

As @RossRidge explained in the comments on your question, this means reassigning IRQ (IV) interrupt vectors.
Because PIC IV overlaps with Intel x86 exception addresses, we must reassign one of them. The exception addresses are tightly coupled, so we need to reassign the PIC vectors.
See also this comment right above the relevant Linus code:

 | well, that went ok, I hope. Now we have to reprogram the interrupts :-( | we put them right after the intel-reserved hardware interrupts, at | int 0x20-0x2F. There they won't mess up anything. Sadly IBM really | messed this up with the original PC, and they haven't been able to | rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, | which is used for the internal hardware interrupts as well. We just | have to reprogram the 8259's, and it isn't fun. 

Now here is the real code. jmp between them is designed to synchronize the CPU and PIC, so the CPU will not send data that the PIC cannot yet receive. This is comparable to the wait states when writing to memory: when the processor is faster than the memory / memory arbiter, the next time it needs to wait a while before accessing the memory.

 mov al,#0x11 | initialization sequence out #0x20,al | send it to 8259A-1 .word 0x00eb,0x00eb | jmp $+2, jmp $+2 out #0xA0,al | and to 8259A-2 .word 0x00eb,0x00eb mov al,#0x20 | start of hardware int (0x20) out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 | start of hardware int 2 (0x28) out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 | 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 | 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 | 8086 mode for both out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb mov al,#0xFF | mask off all interrupts for now out #0x21,al .word 0x00eb,0x00eb out #0xA1,al 
  • Write a pair of ISR handlers (see "Interrupt Maintenance Procedures") for IRQs and Exceptions

For exceptions, you can find the handler code in /kernel/traps.c and /kernel/asm.s .
Some exceptions push the error code onto the stack before moving to the handler that you must pop up, or the iret command will fail. The page error also writes the corresponding virtual address in cr2 .
IRQ handlers are distributed throughout the system. -.- The timer and disk interrupt handlers are in /kernel/system_call.s , for example, the keyboard interrupt handler is in /kernel/keyboard.s .

  • Put the addresses of the ISR handlers in the appropriate descriptors

Exception is thrown in /kernel/traps.c in the trap_init function:

 void trap_init(void) { int i; set_trap_gate(0,&divide_error); set_trap_gate(1,&debug); set_trap_gate(2,&nmi); set_system_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); set_trap_gate(7,&device_not_available); set_trap_gate(8,&double_fault); set_trap_gate(9,&coprocessor_segment_overrun); set_trap_gate(10,&invalid_TSS); set_trap_gate(11,&segment_not_present); set_trap_gate(12,&stack_segment); set_trap_gate(13,&general_protection); set_trap_gate(14,&page_fault); set_trap_gate(15,&reserved); set_trap_gate(16,&coprocessor_error); for (i=17;i<32;i++) set_trap_gate(i,&reserved); /* __asm__("movl $0x3ff000,%%eax\n\t" "movl %%eax,%%db0\n\t" "movl $0x000d0303,%%eax\n\t" "movl %%eax,%%db7" :::"ax");*/ } 

Initializing an IRQ handler entry again spreads over several files. sched_init from /kernel/sched.c initializes the address of the timer interrupt handler, for example.

  • Enable All Supported IRQ Mask Interrupts (PIC)

This is done in /init/main.c in the main function with the sti macro. It is defined in /asm/system.h as follows:

 #define sti() __asm__ ("sti"::) 
+6
source

Source: https://habr.com/ru/post/1239643/


All Articles