asm {
INT_MP_CRASH_ADDR:: //Forward reference to work around compiler
                DU32    &IntMPCrash;

INT_WAKE::
                PUSH    RDX
                PUSH    RAX
                MOV     EAX, &dev
                MOV     EDX, U32 LAPIC_EOI
                MOV     RAX, U64 CDevGlobals.uncached_alias[RAX]
                MOV     U32 [RAX+RDX], 0
                POP     RAX
                POP     RDX
                IRET

IRQ_TIMER::  //I_TIMER
                CALL    TASK_CONTEXT_SAVE
                CLD

                MOV     RAX, U64 [RSP]
                MOV     U64 CTask.rip[RSI], RAX
                MOV     RAX, U64 16[RSP]
                MOV     U64 CTask.rflags[RSI], RAX
                MOV     RAX, U64 24[RSP]
                MOV     U64 CTask.rsp[RSI], RAX

                XOR     RAX, RAX
                MOV     RDI, U64 GS:CCPU.addr[RAX]
                LOCK
                INC     U64 CCPU.total_jiffies[RDI]

                BT              U64 CTask.task_flags[RSI], TASKf_IDLE
                JNC             @@05
                LOCK
                INC     U64 CCPU.idle_pt_hits[RDI]

@@05:   MOV             RAX, U64 CCPU.profiler_timer_irq[RDI]
                TEST    RAX, RAX
                JZ              @@10
                PUSH    RSI
                CALL    RAX             //See ProfTimerInt().
                JMP     @@15
@@10:   ADD     RSP, 8
@@15:   CLI
                MOV             RAX, U64 CCPU.num[RDI]
                TEST    RAX, RAX
                JZ              @@20

                MOV     EAX, &dev
                MOV     EDX, U32 LAPIC_EOI
                MOV     RAX, U64 CDevGlobals.uncached_alias[RAX]
                MOV     U32 [RAX + RDX], 0
                JMP     @@25

@@20:   CALL    &IntCore0TimerHandler   //Only Core 0 calls this.
@@25:   XOR     RAX, RAX
                CMP     RSI, U64 GS:CCPU.idle_task[RAX]
                JE              I32 RESTORE_EXECUTIVE_TASK_IF_READY
                JMP     I32 RESTORE_RSI_TASK

//************************************
INT_FAULT::
                PUSH    RBX
                PUSH    RAX
                MOV     BL, U8 16[RSP]  //We pushed fault_num IntFaultHandlersNew().
                XOR     RAX, RAX
                MOV     FS:U8 CTask.fault_num[RAX], BL
                POP     RAX
                POP     RBX
                ADD     RSP, 8                                  //Pop fault_num

                CALL    TASK_CONTEXT_SAVE

                XOR     RDX, RDX
                MOV     U64 CTask.fault_err_code[RSI], RDX
                MOV     EDX, U32 CTask.fault_num[RSI]
                BT              U64 [INT_FAULT_ERR_CODE_BITMAP], RDX
                JNC     @@1
                POP             U64 CTask.fault_err_code[RSI]

@@1:    MOV     RAX, U64 [RSP]
                MOV     U64 CTask.rip[RSI], RAX
                MOV     RAX, U64 16[RSP]
                MOV     U64 CTask.rflags[RSI], RAX
                MOV     RSP, U64 24[RSP]
                MOV     U64 CTask.rsp[RSI], RSP
                MOV     RBP, CTask.rbp[RSI]
                PUSH    U64 CTask.fault_err_code[RSI]
                PUSH    U64 CTask.fault_num[RSI]
                MOV     RSI, CTask.rsi[RSI]
                CALL    &Fault2                                 //See Fault2
                JMP     I32 RESTORE_FS_TASK

INT_FAULT_ERR_CODE_BITMAP::
                DU32    0x00027D00, 0, 0, 0, 0, 0, 0, 0;
}

U8 *IntEntryGet(I64 irq)
{//Get interrupt handler.
        I64                      handler_addr;
        CIDTEntry       *entry = &dev.idt[irq];

        handler_addr.u16[0] = entry->offset_low;
        handler_addr.u16[1] = entry->offset_mid;
        handler_addr.u32[1] = entry->offset_hi;

        return handler_addr;
}

U8 *IntEntrySet(I64 irq, U0 (*fp_new_handler)(), I64 type=IDTET_IRQ)
{//Set interrupt handler. Returns old handler. See IDTET_IRQ.
//See ::/Demo/Lectures/InterruptDemo.CC.
//See ::/Demo/MultiCore/Interrupts.CC.
        I64                      fp = fp_new_handler;
        U8                      *old_handler;
        CIDTEntry       *entry;

        PUSHFD
        CLI
        old_handler = IntEntryGet(irq);
        entry = &dev.idt[irq];
        entry->seg_select = offset(CGDT.cs64);
        entry->offset_low = fp.u16[0];
        entry->offset_mid = fp.u16[1];
        entry->offset_hi  = fp.u32[1];
        entry->type_attr  = 0x80 + type; //bit 7 is 'segment present'
        entry->ist = entry->zero = 0; //We don't care about the IST mechanism
        POPFD

        return old_handler;
}

U0 IntPICInit()
{//Init 8259
        OutU8(PIC_1, PIC_INIT); //IW (Initialization Word) 1
        OutU8(PIC_2, PIC_INIT); //IW1

        OutU8(PIC_1_DATA, 0x20); //IW2 Moving IRQ base from 0 -> 32 (beyond Intel reserved faults)
        OutU8(PIC_2_DATA, 0x28); //IW2 Moving IRQ base from 8 -> 40
        OutU8(PIC_1_DATA, 0x04); //IW3 Telling PIC_1 PIC_2 exists.
        OutU8(PIC_2_DATA, 0x02); //IW3 Telling PIC_2 its cascade identity.
        OutU8(PIC_1_DATA, 0x0D); //IW4 8086 Mode, Buffered Mode (Master PIC)
        OutU8(PIC_2_DATA, 0x09); //IW4 8086 Mode, Buffered Mode (Slave PIC)
        OutU8(PIC_1_DATA, 0xFA); //Mask all but IRQ0 (timer) and IRQ2 Cascade.
        OutU8(PIC_2_DATA, 0xFF);
}

interrupt U0 IntNop()
{//Make unplanned IRQs stop by all means!
        OutU8(PIC_2, PIC_EOI);
        OutU8(PIC_1, PIC_EOI);
        *(dev.uncached_alias + LAPIC_EOI)(U32 *) = 0;
}

interrupt U0 IntDivZero()
{
        if (Gs->num)
        {
                mp_count = 1;
                debug.mp_crash->cpu_num = Gs->num;
                debug.mp_crash->task = Fs;
                MOV RAX, U64 8[RBP] //Get RIP off of stack.
                debug.mp_crash->rip = RAXGet;
                debug.mp_crash->message = "Div Zero";
                debug.mp_crash->message_num = 0;
                MPInt(I_MP_CRASH, 0);
                SysHlt;
        }
        throw('DivZero');
}

U8 *IntFaultHandlersNew()
{
        I64 i;
        U8 *res = MAlloc(256 * 7, Fs->code_heap), *dst = res;

        for (i = 0; i < 256; i++)
        {
                *dst++ = 0x6A; //PUSH I8 xx
                *dst(I8 *)++ = i;
                *dst++ = 0xE9; //JMP    I32 xxxxxxxx
                *dst(I32 *) = INT_FAULT - dst - 4;
                dst += 4;
        }

        return res;
}

U0 IntInit1()
{//Interrupt descriptor table part1.
        I64                             i;
        CSysLimitBase   tmp_ptr;

        if (!Gs->num) //Gs is current CCPU struct
        {
                dev.idt = CAllocAligned(sizeof(CIDTEntry) * 256, 8);
                for (i = 0; i < 256; i++)
                        IntEntrySet(i, &IntNop);
        }
        tmp_ptr.limit = 256 * sizeof(CIDTEntry) - 1;
        tmp_ptr.base = dev.idt;
        RAXSet(&tmp_ptr);
        LIDT U64 [RAX]
}

U0 IntInit2()
{//Interrupt descriptor table part2: Core 0 Only.
        I64 i;

        PUSHFD
        CLI
        IntEntrySet(I_DIV_ZERO, &IntDivZero);
        for (i = 1; i < 32; i++)
                IntEntrySet(i, &debug.int_fault_code[7 * i]);
/*In theory, we use the PIC mask reg to insure we don't get
anything but keyboard, mouse and timer IRQs.    In practice, I've
gotten IRQ 0x27, perhaps because I didn't initialize the APIC.
I go ahead and ACK PIC in IntNop().
I have no idea why I got a IRQ 0x27.
*/
        IntEntrySet(I_NMI,               _SYS_HLT);
        IntEntrySet(I_TIMER,     IRQ_TIMER);
        IntEntrySet(I_MP_CRASH, *INT_MP_CRASH_ADDR(U32 *));
        IntEntrySet(I_WAKE,              INT_WAKE);
        IntEntrySet(I_DEBUG,    &debug.int_fault_code[7 * I_DEBUG]);
        POPFD
}