| /* |
| * core/fs/pxe/isr.c |
| * |
| * Stub invoked on return from real mode including from an interrupt. |
| * Interrupts are locked out on entry. |
| */ |
| |
| #include "core.h" |
| #include "thread.h" |
| #include "pxe.h" |
| #include <string.h> |
| #include <sys/cpu.h> |
| #include <sys/io.h> |
| |
| extern uint8_t pxe_irq_pending; |
| extern volatile uint8_t pxe_need_poll; |
| static DECLARE_INIT_SEMAPHORE(pxe_receive_thread_sem, 0); |
| static DECLARE_INIT_SEMAPHORE(pxe_poll_thread_sem, 0); |
| static struct thread *pxe_thread, *poll_thread; |
| |
| #ifndef PXE_POLL_FORCE |
| # define PXE_POLL_FORCE 0 |
| #endif |
| |
| #ifndef PXE_POLL_BY_MODEL |
| # define PXE_POLL_BY_MODEL 1 |
| #endif |
| |
| /* |
| * Note: this *must* be called with interrupts enabled. |
| */ |
| static bool install_irq_vector(uint8_t irq, void (*isr)(void), far_ptr_t *old) |
| { |
| far_ptr_t *entry; |
| unsigned int vec; |
| uint8_t mask, mymask; |
| uint32_t now; |
| bool ok; |
| |
| if (irq < 8) |
| vec = irq + 0x08; |
| else if (irq < 16) |
| vec = (irq - 8) + 0x70; |
| else |
| return false; |
| |
| cli(); |
| |
| if (pxe_need_poll) { |
| sti(); |
| return false; |
| } |
| |
| entry = (far_ptr_t *)(vec << 2); |
| *old = *entry; |
| entry->ptr = (uint32_t)isr; |
| |
| /* Enable this interrupt at the PIC level, just in case... */ |
| mymask = ~(1 << (irq & 7)); |
| if (irq >= 8) { |
| mask = inb(0x21); |
| mask &= ~(1 << 2); /* Enable cascade */ |
| outb(mask, 0x21); |
| mask = inb(0xa1); |
| mask &= mymask; |
| outb(mask, 0xa1); |
| } else { |
| mask = inb(0x21); |
| mask &= mymask; |
| outb(mask, 0x21); |
| } |
| |
| sti(); |
| |
| now = jiffies(); |
| |
| /* Some time to watch for stuck interrupts */ |
| while (jiffies() - now < 4 && (ok = !pxe_need_poll)) |
| hlt(); |
| |
| if (!ok) |
| *entry = *old; /* Restore the old vector */ |
| |
| ddprintf("UNDI: IRQ %d(0x%02x): %04x:%04x -> %04x:%04x\n", irq, vec, |
| old->seg, old->offs, entry->seg, entry->offs); |
| |
| return ok; |
| } |
| |
| static bool uninstall_irq_vector(uint8_t irq, void (*isr), far_ptr_t *old) |
| { |
| far_ptr_t *entry; |
| unsigned int vec; |
| bool rv; |
| |
| if (!irq) |
| return true; /* Nothing to uninstall */ |
| |
| if (irq < 8) |
| vec = irq + 0x08; |
| else if (irq < 16) |
| vec = (irq - 8) + 0x70; |
| else |
| return false; |
| |
| cli(); |
| |
| entry = (far_ptr_t *)(vec << 2); |
| |
| if (entry->ptr != (uint32_t)isr) { |
| rv = false; |
| } else { |
| *entry = *old; |
| rv = true; |
| } |
| |
| sti(); |
| return rv; |
| } |
| |
| static void pxe_poll_wakeups(void) |
| { |
| static jiffies_t last_jiffies = 0; |
| jiffies_t now = jiffies(); |
| |
| if (pxe_need_poll == 1) { |
| /* If we need polling now, activate polling */ |
| pxe_need_poll = 3; |
| sem_up(&pxe_poll_thread_sem); |
| } |
| |
| if (now != last_jiffies) { |
| last_jiffies = now; |
| __thread_process_timeouts(); |
| } |
| |
| if (pxe_irq_pending) { |
| pxe_irq_pending = 0; |
| sem_up(&pxe_receive_thread_sem); |
| } |
| } |
| |
| static void pxe_process_irq(void) |
| { |
| static __lowmem t_PXENV_UNDI_ISR isr; |
| |
| uint16_t func = PXENV_UNDI_ISR_IN_PROCESS; /* First time */ |
| bool done = false; |
| |
| while (!done) { |
| memset(&isr, 0, sizeof isr); |
| isr.FuncFlag = func; |
| func = PXENV_UNDI_ISR_IN_GET_NEXT; /* Next time */ |
| |
| pxe_call(PXENV_UNDI_ISR, &isr); |
| |
| switch (isr.FuncFlag) { |
| case PXENV_UNDI_ISR_OUT_DONE: |
| done = true; |
| break; |
| |
| case PXENV_UNDI_ISR_OUT_TRANSMIT: |
| /* Transmit complete - nothing for us to do */ |
| break; |
| |
| case PXENV_UNDI_ISR_OUT_RECEIVE: |
| undiif_input(&isr); |
| break; |
| |
| case PXENV_UNDI_ISR_OUT_BUSY: |
| /* ISR busy, this should not happen */ |
| done = true; |
| break; |
| |
| default: |
| /* Invalid return code, this should not happen */ |
| done = true; |
| break; |
| } |
| } |
| } |
| |
| static void pxe_receive_thread(void *dummy) |
| { |
| (void)dummy; |
| |
| for (;;) { |
| sem_down(&pxe_receive_thread_sem, 0); |
| pxe_process_irq(); |
| } |
| } |
| |
| static bool pxe_isr_poll(void) |
| { |
| static __lowmem t_PXENV_UNDI_ISR isr; |
| |
| isr.FuncFlag = PXENV_UNDI_ISR_IN_START; |
| pxe_call(PXENV_UNDI_ISR, &isr); |
| |
| return isr.FuncFlag == PXENV_UNDI_ISR_OUT_OURS; |
| } |
| |
| static void pxe_poll_thread(void *dummy) |
| { |
| (void)dummy; |
| |
| /* Block indefinitely unless activated */ |
| sem_down(&pxe_poll_thread_sem, 0); |
| |
| for (;;) { |
| cli(); |
| if (pxe_receive_thread_sem.count < 0 && pxe_isr_poll()) |
| sem_up(&pxe_receive_thread_sem); |
| else |
| __schedule(); |
| sti(); |
| cpu_relax(); |
| } |
| } |
| |
| /* |
| * This does preparations and enables the PXE thread |
| */ |
| void pxe_init_isr(void) |
| { |
| start_idle_thread(); |
| sched_hook_func = pxe_poll_wakeups; |
| /* |
| * Run the pxe receive thread at elevated priority, since the UNDI |
| * stack is likely to have very limited memory available; therefore to |
| * avoid packet loss we need to move it into memory that we ourselves |
| * manage, as soon as possible. |
| */ |
| core_pm_hook = __schedule; |
| |
| pxe_thread = start_thread("pxe receive", 16384, -20, |
| pxe_receive_thread, NULL); |
| } |
| |
| /* |
| * Actually start the interrupt routine inside the UNDI stack |
| */ |
| void pxe_start_isr(void) |
| { |
| int irq = pxe_undi_info.IntNumber; |
| |
| if (irq == 2) |
| irq = 9; /* IRQ 2 is really IRQ 9 */ |
| else if (irq > 15) |
| irq = 0; /* Invalid IRQ */ |
| |
| pxe_irq_vector = irq; |
| |
| if (irq) { |
| if (!install_irq_vector(irq, pxe_isr, &pxe_irq_chain)) |
| irq = 0; /* Install failed or stuck interrupt */ |
| } |
| |
| poll_thread = start_thread("pxe poll", 4096, POLL_THREAD_PRIORITY, |
| pxe_poll_thread, NULL); |
| |
| if (!irq || !(pxe_undi_iface.ServiceFlags & PXE_UNDI_IFACE_FLAG_IRQ)) { |
| asm volatile("orb $1,%0" : "+m" (pxe_need_poll)); |
| dprintf("pxe_start_isr: forcing pxe_need_poll\n"); |
| } else if (PXE_POLL_BY_MODEL) { |
| dprintf("pxe_start_isr: trying poll by model\n"); |
| int hwad = ((int)MAC[0] << 16) + ((int)MAC[1] << 8) + MAC[2]; |
| dprintf("pxe_start_isr: got %06x %04x\n", hwad, pxe_undi_iface.ServiceFlags); |
| if ((hwad == 0x000023ae) && (pxe_undi_iface.ServiceFlags == 0xdc1b) || |
| (hwad == 0x005c260a) && (pxe_undi_iface.ServiceFlags == 0xdc1b) || |
| (hwad == 0x00180373) && (pxe_undi_iface.ServiceFlags == 0xdc1b)) { |
| asm volatile("orb $1,%0" : "+m" (pxe_need_poll)); |
| dprintf("pxe_start_isr: forcing pxe_need_poll by model\n"); |
| } |
| } |
| } |
| |
| int reset_pxe(void) |
| { |
| static __lowmem struct s_PXENV_UNDI_CLOSE undi_close; |
| |
| sched_hook_func = NULL; |
| core_pm_hook = core_pm_null_hook; |
| kill_thread(pxe_thread); |
| |
| memset(&undi_close, 0, sizeof(undi_close)); |
| pxe_call(PXENV_UNDI_CLOSE, &undi_close); |
| |
| if (undi_close.Status) |
| printf("PXENV_UNDI_CLOSE failed: 0x%x\n", undi_close.Status); |
| |
| if (pxe_irq_vector) |
| uninstall_irq_vector(pxe_irq_vector, pxe_isr, &pxe_irq_chain); |
| if (poll_thread) |
| kill_thread(poll_thread); |
| |
| return undi_close.Status; |
| } |