#include "interrupts.h" #include "string.h" #include "stdio.h" #include "kernel.h" #include "keyboard.h" // the Present bit in a IDT entry #define IDT_PRESENT_BIT 0x80 // DPL rings #define KERNEL_RING 0 // types of IDT entries #define IDT_TYPE_INTERRUPT_GATE 0xE // PIC constants #define ICW1_ICW4 0x01 // support a 4th initialization word (ICW4) #define ICW1_INIT 0x10 // bit indicating initialization of chip // offset for hardware interrupts (for PIC ICW2) #define ICW2_IRQ_BASE_MASTER IRQ_BASE #define ICW2_IRQ_BASE_SLAVE ICW2_IRQ_BASE_MASTER + 8 // IRQ selection (master and slave, ICW3) #define ICW3_MASTER_IR0 0x01 #define ICW3_MASTER_IR1 0x02 #define ICW3_MASTER_IR2 0x04 #define ICW3_MASTER_IR3 0x08 #define ICW3_MASTER_IR4 0x10 #define ICW3_MASTER_IR5 0x20 #define ICW3_MASTER_IR6 0x40 #define ICW3_MASTER_IR7 0x80 #define ICW3_SLAVE_IR0 0x00 #define ICW3_SLAVE_IR1 0x01 #define ICW3_SLAVE_IR2 0x02 #define ICW3_SLAVE_IR3 0x03 #define ICW3_SLAVE_IR4 0x04 #define ICW3_SLAVE_IR5 0x05 #define ICW3_SLAVE_IR6 0x06 #define ICW3_SLAVE_IR7 0x07 // special mode PIC ICW4 #define ICW4_8086 0x01 #define ICW4_AUTO_ACK_EOI 0x02 #define ICW4_BUF_MASTER 0x04 #define ICW4_BUFFER 0x08 #define ICW4_FULLY_NESTED 0x10 // OCW2 #define OCW2_EOI 0x20 // non-specific End Of Interrupt void interrupt_handler_init_void( interrupt_handler_t *handler, struct interrupt_t *interrupt ) { memset( handler, 0, sizeof( interrupt_handler_t ) ); handler->interrupt = interrupt; } void interrupt_handler_init( interrupt_handler_t *handler, uint8_t interrupt_no, struct interrupt_t *interrupt, interrupt_handler_func_t handle, void *driver ) { memset( handler, 0, sizeof( interrupt_handler_t ) ); handler->interrupt_no = interrupt_no; handler->interrupt = interrupt; handler->handle = handle; handler->driver = driver; } // must be global: when called via assembly code we loose the context // of the interrupt handler structure static interrupt_t *active_interrupt; void interrupts_init( interrupt_t *interrupt, uint16_t gdt_code_segment_selector, task_manager_t *task_manager ) { memset( interrupt, 0, sizeof( interrupt_t ) ); interrupt_handler_t empty_interrupt_handler; interrupt_handler_init_void( &empty_interrupt_handler, interrupt ); interrupt->task_manager = task_manager; for( int i = 0; i < NOF_INTERRUPTS; i++ ) { interrupts_register_interrupt_gate( interrupt, i, gdt_code_segment_selector, &interrupts_ignore_request, KERNEL_RING, IDT_TYPE_INTERRUPT_GATE ); interrupts_register_interrupt_handler( empty_interrupt_handler ); } // divide-by-zero exception 0x00 interrupt_handler_t divide_by_zero_interrupt_handler; interrupt_handler_init( ÷_by_zero_interrupt_handler, 0x00, interrupt, interrupts_exception_division_by_zero, NULL ); interrupts_register_interrupt_gate( interrupt, 0x00, gdt_code_segment_selector, &interrupts_handle_exception_0x00, KERNEL_RING, IDT_TYPE_INTERRUPT_GATE ); interrupts_register_interrupt_handler( divide_by_zero_interrupt_handler ); // IRQ 0 - PIT - programmable interrupt timer interrupt_handler_t pit_interrupt_handler; interrupt_handler_init( &pit_interrupt_handler, IRQ_BASE + 0x00, interrupt, interrupts_interrupt_PIT, NULL ); interrupts_register_interrupt_gate( interrupt, IRQ_BASE + 0x00, gdt_code_segment_selector, &interrupts_handle_irq_0x00, KERNEL_RING, IDT_TYPE_INTERRUPT_GATE ); interrupts_register_interrupt_handler( pit_interrupt_handler ); // IRQ 1 - PS/2 keyboard interrupts_register_interrupt_gate( interrupt, IRQ_BASE + 0x01, gdt_code_segment_selector, &interrupts_handle_irq_0x01, KERNEL_RING, IDT_TYPE_INTERRUPT_GATE ); // IRQ 12 - PS/2 mouse interrupts_register_interrupt_gate( interrupt, IRQ_BASE + 0x0C, gdt_code_segment_selector, &interrupts_handle_irq_0x0C, KERNEL_RING, IDT_TYPE_INTERRUPT_GATE ); port8_init( &interrupt->PIC_master_control, 0x20 ); port8_init( &interrupt->PIC_master_data, 0x21 ); port8_init( &interrupt->PIC_slave_control, 0xA0 ); port8_init( &interrupt->PIC_slave_data, 0xA1 ); // initialize hardware management PICs (ICW1) port8_write( &interrupt->PIC_master_control, ICW1_ICW4 | ICW1_INIT ); port8_write( &interrupt->PIC_slave_control, ICW1_ICW4 | ICW1_INIT ); // set IRQ base of both PICS (ICW2), remap them so they // don't collide with CPU exceptions (this is true after 0x20) port8_write( &interrupt->PIC_master_data, ICW2_IRQ_BASE_MASTER ); port8_write( &interrupt->PIC_slave_data, ICW2_IRQ_BASE_SLAVE ); // use IRQ2 for master slave communication (ICW3) port8_write( &interrupt->PIC_master_data, ICW3_MASTER_IR2 ); port8_write( &interrupt->PIC_slave_data, ICW3_SLAVE_IR2 ); // unbuffered, manual acknoledgment and 8086 mode (ICW4) // TODO: use buffered mode later? port8_write( &interrupt->PIC_master_data, ICW4_8086 ); port8_write( &interrupt->PIC_slave_data, ICW4_8086 ); // done, enable all IRQs by setting the interrupt mask register // 0 for all bits. We are not interested in the old state of // the mask as we are initializing in protected mode // TODO: later we will mask uninmported interrupts maybe? port8_write( &interrupt->PIC_master_data, 0 ); port8_write( &interrupt->PIC_slave_data, 0 ); // tell CPU where the IDT is interrupt->idt_pointer.size = NOF_INTERRUPTS * sizeof( interrupt_gate_descriptor_t ) - 1; interrupt->idt_pointer.base = (uint32_t)interrupt->descriptor_table; interrupts_load_idt( &interrupt->idt_pointer ); active_interrupt = interrupt; } void interrupts_register_interrupt_gate( interrupt_t *interrupts, uint8_t interrupt_no, uint16_t gdt_code_segment_selector, void (*helper_handler)( ), uint8_t privilege_level, uint8_t descriptor_type ) { interrupt_gate_descriptor_t *descr = &interrupts->descriptor_table[interrupt_no]; descr->handler_address_low_bits = ( (uint32_t )helper_handler ) & 0xFFFF; descr->handler_address_high_bits = ( ( (uint32_t )helper_handler ) >> 16 ) & 0xFFFF; descr->gdt_code_segment_selector = gdt_code_segment_selector; descr->access = IDT_PRESENT_BIT | (( privilege_level & 0x3 ) << 5 ) | descriptor_type; descr->reserved = 0; } void interrupts_register_interrupt_handler( interrupt_handler_t handler ) { handler.interrupt->interrupt_handler[handler.interrupt_no] = handler; } uint32_t interrupts_exception_division_by_zero( interrupt_handler_t *handler, uint32_t esp ) { // TODO: caused by kernel code or user land code? or a task we can kill? // For now, the kernel has no task management, so we panic kernel_panic( "Division by zero with ESP 0x%X\n", esp ); return esp; } uint32_t interrupts_interrupt_PIT( interrupt_handler_t *handler, uint32_t esp ) { // for now do noting with the timer tick return esp; } uint32_t interrupts_handle_static_interrupt( uint8_t interrupt_no, uint32_t esp ) { if( active_interrupt == NULL ) { kernel_panic( "No active interrupt handler!" ); } return interrupts_handle_interrupt( active_interrupt, interrupt_no, esp ); } uint32_t interrupts_handle_interrupt( interrupt_t *interrupt, uint8_t interrupt_no, uint32_t esp ) { interrupt_handler_t *handler = &interrupt->interrupt_handler[interrupt_no]; if( handler->handle == NULL ) { kernel_panic( "Unhandled interrupt 0x%X with ESP 0x%X\n", interrupt_no, esp ); } uint32_t new_esp = handler->handle( handler, esp ); // timer interrupt, schedule the tasks if( interrupt_no == IRQ_BASE + 0x00 ) { new_esp = (uint32_t)task_manager_schedule_task( interrupt->task_manager, (cpu_state_t *)esp ); } // send ACK to PIC for hardware interrupts if( interrupt_no >= IRQ_BASE && interrupt_no <= IRQ_BASE + 16 ) { port8_write( &interrupt->PIC_master_control, OCW2_EOI ); if( interrupt_no >= IRQ_BASE + 8 ) { port8_write( &interrupt->PIC_slave_control, OCW2_EOI ); } } return new_esp; }