/* EOI is actually sent to the local APIC, not the I/O APIC as the function's * name would indicate. */ static void ioapic_send_eoi(int irq) { assert(!x86_irq_is_enabled()); struct lapic_info *apic = &cpu_apics[0]; /* the specs say that 0 is the right thing to write here. */ mm_outl(apic->base_addr + APIC_EOI, 0); }
/* there are at most 256 32-bit registers in a pre-2013 IOAPIC. */ static inline void ioapic_write( struct ioapic_info *ioa, uint8_t address, uint32_t value) { assert(!x86_irq_is_enabled()); mm_outb(ioa->base_addr + 0, address); /* IOREGSEL */ mm_outl(ioa->base_addr + 0x10, value); /* IOWIN */ }
static irq_handler_fn choose_handler(int vecn) { assert(!x86_irq_is_enabled()); irq_handler_fn handler = NULL; if(vecn == 0x20) handler = &on_preempt; else if(vecn <= max_irq_handler) handler = irq_handlers[vecn]; if(handler == NULL) handler = &int_trigger; return handler; }
static void ioapic_mask_irq(int irq) { assert(!x86_irq_is_enabled()); struct ioapic_info *ioa = &global_ioapics[irq / 24]; int vec = irq % 24; uint32_t ctl = ioapic_read(ioa, IOREDTBL(vec) + 0); ctl |= 1 << 16; ioapic_write(ioa, IOREDTBL(vec) + 0, ctl); }
void isr_irq_bottom(struct x86_exregs *regs) { assert(!x86_irq_is_enabled()); int vecn = regs->reason; (*global_pic.send_eoi)(vecn - 0x20); irq_handler_fn handler = choose_handler(vecn); if(handler == &int_trigger) { (*global_pic.mask_irq)(vecn - 0x20); vecn = -vecn; } if(irq_in_kernel(regs) && !kernel_irq_ok) { /* kernel code was interrupted. mark and proceed. */ irq_defer(vecn); } else if(irq_in_kernel(regs)) { /* the idling kernel was interrupted. */ assert(kernel_irq_ok); kernel_irq_ok = false; x86_irq_enable(); struct thread *next = (*handler)(vecn); x86_irq_disable(); if(irq_defer_active) save_user_ex(regs); return_from_irq(next); /* going back to schedule(). */ kernel_irq_ok = true; } else { /* userspace was interrupted. */ assert(!kernel_irq_ok); assert(vecn > max_irq_handler || deferred_vecs[vecn] == 0); x86_irq_enable(); struct thread *next = (*handler)(vecn); x86_irq_disable(); if(irq_defer_active) save_user_ex(regs); return_from_irq(next); /* if return_from_irq() returned, current thread wasn't preempted. */ } }
/* call latent interrupt handlers, resolve preemptions, return the winner. * caller must check for retval == current && I ∈ current.PreemptFlags, and * engage max_delay as appropriate. */ struct thread *irq_call_deferred(struct thread *next) { assert(!x86_irq_is_enabled()); assert(!kernel_irq_ok); /* initialize resolution state, feed it a primary event. */ struct thread *current = get_current_thread(); void *cur_utcb = current != NULL ? thread_get_utcb(current) : NULL; next = sched_resolve_next(current, cur_utcb, current, next); int n_defer_masks = (max_irq_handler + WORD_BITS) / WORD_BITS, n_done; do { if(!irq_defer_active) break; n_done = 0; for(int i=0; i < n_defer_masks; i++) { L4_Word_t m = defer_set_masks[i]; while(m != 0) { int b = ffsl(m) - 1; assert(b >= 0); m &= ~(1ul << b); int vecn = i * WORD_BITS + b; int n_defers = deferred_vecs[vecn]; assert(n_defers > 0); irq_handler_fn handler = choose_handler(vecn); deferred_vecs[vecn] = 0; defer_set_masks[i] &= ~(1ul << b); x86_irq_enable(); next = sched_resolve_next(current, cur_utcb, next, (*handler)(n_defers > 1 ? -vecn : vecn)); x86_irq_disable(); n_done++; } } } while(n_done > 0); irq_defer_active = false; return next; }
static void irq_defer(int vec_num) { assert(!x86_irq_is_enabled()); assert(abs(vec_num) <= max_irq_handler); /* to solve, fix PIC init */ vec_num = abs(vec_num); irq_defer_active = true; defer_set_masks[vec_num / WORD_BITS] |= 1ul << (vec_num % WORD_BITS); /* handle unsigned wraparound. */ if(unlikely(deferred_vecs[vec_num] == 0xff)) { deferred_vecs[vec_num] = 1; } if(++deferred_vecs[vec_num] > 1) { /* TODO: translate bare vector numbers to IRQ, or redefine mask_irq to * map vector numbers. */ if(vec_num >= 0x20 && vec_num <= 0x2f) { (*global_pic.mask_irq)(vec_num - 0x20); } } }
static inline uint32_t ioapic_read(struct ioapic_info *ioa, uint8_t address) { assert(!x86_irq_is_enabled()); mm_outb(ioa->base_addr + 0, address); return mm_inl(ioa->base_addr + 0x10); }