| #include "libcflat.h" |
| #include "apic.h" |
| #include "vm.h" |
| #include "smp.h" |
| #include "desc.h" |
| #include "isr.h" |
| #include "delay.h" |
| |
| static void poll_remote_irr(unsigned line) |
| { |
| while (ioapic_read_redir(line).remote_irr) |
| cpu_relax(); |
| } |
| |
| static void toggle_irq_line(unsigned line) |
| { |
| set_irq_line(line, 1); |
| set_irq_line(line, 0); |
| } |
| |
| static void ioapic_reg_version(void) |
| { |
| u8 version_offset; |
| uint32_t data_read, data_write; |
| |
| version_offset = 0x01; |
| data_read = ioapic_read_reg(version_offset); |
| data_write = data_read ^ 0xffffffff; |
| |
| ioapic_write_reg(version_offset, data_write); |
| report(data_read == ioapic_read_reg(version_offset), |
| "version register read only test"); |
| } |
| |
| static void ioapic_reg_id(void) |
| { |
| u8 id_offset; |
| uint32_t data_read, data_write, diff; |
| |
| id_offset = 0x0; |
| data_read = ioapic_read_reg(id_offset); |
| data_write = data_read ^ 0xffffffff; |
| |
| ioapic_write_reg(id_offset, data_write); |
| |
| diff = data_read ^ ioapic_read_reg(id_offset); |
| report(diff == 0x0f000000, "id register only bits [24:27] writable"); |
| } |
| |
| static void ioapic_arbitration_id(void) |
| { |
| u8 id_offset, arb_offset; |
| uint32_t write; |
| |
| id_offset = 0x0; |
| arb_offset = 0x2; |
| write = 0x0f000000; |
| |
| ioapic_write_reg(id_offset, write); |
| report(ioapic_read_reg(arb_offset) == write, |
| "arbitration register set by id"); |
| |
| ioapic_write_reg(arb_offset, 0x0); |
| report(ioapic_read_reg(arb_offset) == write, |
| "arbtration register read only"); |
| } |
| |
| static volatile int g_isr_76; |
| |
| static void ioapic_isr_76(isr_regs_t *regs) |
| { |
| ++g_isr_76; |
| eoi(); |
| } |
| |
| static void test_ioapic_edge_intr(void) |
| { |
| handle_irq(0x76, ioapic_isr_76); |
| ioapic_set_redir(0x0e, 0x76, TRIGGER_EDGE); |
| toggle_irq_line(0x0e); |
| asm volatile ("nop"); |
| report(g_isr_76 == 1, "edge triggered intr"); |
| } |
| |
| static volatile int g_isr_77; |
| |
| static void ioapic_isr_77(isr_regs_t *regs) |
| { |
| ++g_isr_77; |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_intr(void) |
| { |
| handle_irq(0x77, ioapic_isr_77); |
| ioapic_set_redir(0x0e, 0x77, TRIGGER_LEVEL); |
| set_irq_line(0x0e, 1); |
| asm volatile ("nop"); |
| report(g_isr_77 == 1, "level triggered intr"); |
| } |
| |
| static int g_78, g_66, g_66_after_78; |
| static ulong g_66_rip, g_78_rip; |
| |
| static void ioapic_isr_78(isr_regs_t *regs) |
| { |
| ++g_78; |
| g_78_rip = regs->rip; |
| eoi(); |
| } |
| |
| static void ioapic_isr_66(isr_regs_t *regs) |
| { |
| ++g_66; |
| if (g_78) |
| ++g_66_after_78; |
| g_66_rip = regs->rip; |
| eoi(); |
| } |
| |
| static void test_ioapic_simultaneous(void) |
| { |
| handle_irq(0x78, ioapic_isr_78); |
| handle_irq(0x66, ioapic_isr_66); |
| ioapic_set_redir(0x0e, 0x78, TRIGGER_EDGE); |
| ioapic_set_redir(0x0f, 0x66, TRIGGER_EDGE); |
| cli(); |
| toggle_irq_line(0x0f); |
| toggle_irq_line(0x0e); |
| sti_nop(); |
| report(g_66 && g_78 && g_66_after_78 && g_66_rip == g_78_rip, |
| "ioapic simultaneous edge interrupts"); |
| } |
| |
| static volatile int g_tmr_79 = -1; |
| |
| static void ioapic_isr_79(isr_regs_t *regs) |
| { |
| g_tmr_79 = apic_read_bit(APIC_TMR, 0x79); |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_edge_tmr(bool expected_tmr_before) |
| { |
| int tmr_before; |
| |
| handle_irq(0x79, ioapic_isr_79); |
| ioapic_set_redir(0x0e, 0x79, TRIGGER_EDGE); |
| tmr_before = apic_read_bit(APIC_TMR, 0x79); |
| toggle_irq_line(0x0e); |
| asm volatile ("nop"); |
| report(tmr_before == expected_tmr_before && !g_tmr_79, |
| "TMR for ioapic edge interrupts (expected %s)", |
| expected_tmr_before ? "true" : "false"); |
| } |
| |
| static void test_ioapic_level_tmr(bool expected_tmr_before) |
| { |
| int tmr_before; |
| |
| handle_irq(0x79, ioapic_isr_79); |
| ioapic_set_redir(0x0e, 0x79, TRIGGER_LEVEL); |
| tmr_before = apic_read_bit(APIC_TMR, 0x79); |
| set_irq_line(0x0e, 1); |
| asm volatile ("nop"); |
| report(tmr_before == expected_tmr_before && g_tmr_79, |
| "TMR for ioapic level interrupts (expected %s)", |
| expected_tmr_before ? "true" : "false"); |
| } |
| |
| static void toggle_irq_line_0x0e(void *data) |
| { |
| cli(); |
| delay(IPI_DELAY); |
| toggle_irq_line(0x0e); |
| sti(); |
| } |
| |
| static void test_ioapic_edge_tmr_smp(bool expected_tmr_before) |
| { |
| int tmr_before; |
| int i; |
| |
| g_tmr_79 = -1; |
| handle_irq(0x79, ioapic_isr_79); |
| ioapic_set_redir(0x0e, 0x79, TRIGGER_EDGE); |
| tmr_before = apic_read_bit(APIC_TMR, 0x79); |
| on_cpu_async(1, toggle_irq_line_0x0e, 0); |
| i = 0; |
| while(g_tmr_79 == -1) i++; |
| printf("%d iterations before interrupt received\n", i); |
| report(tmr_before == expected_tmr_before && !g_tmr_79, |
| "TMR for ioapic edge interrupts (expected %s)", |
| expected_tmr_before ? "true" : "false"); |
| } |
| |
| static void set_irq_line_0x0e(void *data) |
| { |
| cli(); |
| delay(IPI_DELAY); |
| set_irq_line(0x0e, 1); |
| sti(); |
| } |
| |
| static void test_ioapic_level_tmr_smp(bool expected_tmr_before) |
| { |
| int i, tmr_before; |
| |
| g_tmr_79 = -1; |
| handle_irq(0x79, ioapic_isr_79); |
| ioapic_set_redir(0x0e, 0x79, TRIGGER_LEVEL); |
| tmr_before = apic_read_bit(APIC_TMR, 0x79); |
| on_cpu_async(1, set_irq_line_0x0e, 0); |
| i = 0; |
| while(g_tmr_79 == -1) i++; |
| printf("%d iterations before interrupt received\n", i); |
| report(tmr_before == expected_tmr_before && g_tmr_79, |
| "TMR for ioapic level interrupts (expected %s)", |
| expected_tmr_before ? "true" : "false"); |
| poll_remote_irr(0xe); |
| } |
| |
| static int g_isr_98; |
| |
| static void ioapic_isr_98(isr_regs_t *regs) |
| { |
| ++g_isr_98; |
| if (g_isr_98 == 1) { |
| set_irq_line(0x0e, 0); |
| set_irq_line(0x0e, 1); |
| } |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_coalesce(void) |
| { |
| handle_irq(0x98, ioapic_isr_98); |
| ioapic_set_redir(0x0e, 0x98, TRIGGER_LEVEL); |
| set_irq_line(0x0e, 1); |
| asm volatile ("nop"); |
| report(g_isr_98 == 1, "coalesce simultaneous level interrupts"); |
| } |
| |
| static int g_isr_99; |
| |
| static void ioapic_isr_99(isr_regs_t *regs) |
| { |
| ++g_isr_99; |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_sequential(void) |
| { |
| handle_irq(0x99, ioapic_isr_99); |
| ioapic_set_redir(0x0e, 0x99, TRIGGER_LEVEL); |
| set_irq_line(0x0e, 1); |
| set_irq_line(0x0e, 1); |
| asm volatile ("nop"); |
| report(g_isr_99 == 2, "sequential level interrupts"); |
| } |
| |
| static volatile int g_isr_9a; |
| |
| static void ioapic_isr_9a(isr_regs_t *regs) |
| { |
| ++g_isr_9a; |
| if (g_isr_9a == 2) |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_retrigger(void) |
| { |
| int i; |
| |
| handle_irq(0x9a, ioapic_isr_9a); |
| ioapic_set_redir(0x0e, 0x9a, TRIGGER_LEVEL); |
| |
| asm volatile ("cli"); |
| set_irq_line(0x0e, 1); |
| |
| for (i = 0; i < 10; i++) { |
| if (g_isr_9a == 2) |
| break; |
| |
| asm volatile ("sti; hlt; cli"); |
| } |
| |
| asm volatile ("sti"); |
| |
| report(g_isr_9a == 2, "retriggered level interrupts without masking"); |
| } |
| |
| static volatile int g_isr_81; |
| |
| static void ioapic_isr_81(isr_regs_t *regs) |
| { |
| ++g_isr_81; |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_edge_mask(void) |
| { |
| handle_irq(0x81, ioapic_isr_81); |
| ioapic_set_redir(0x0e, 0x81, TRIGGER_EDGE); |
| |
| set_mask(0x0e, true); |
| set_irq_line(0x0e, 1); |
| set_irq_line(0x0e, 0); |
| |
| asm volatile ("nop"); |
| report(g_isr_81 == 0, "masked level interrupt"); |
| |
| set_mask(0x0e, false); |
| set_irq_line(0x0e, 1); |
| |
| asm volatile ("nop"); |
| report(g_isr_81 == 1, "unmasked level interrupt"); |
| } |
| |
| static volatile int g_isr_82; |
| |
| static void ioapic_isr_82(isr_regs_t *regs) |
| { |
| ++g_isr_82; |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_mask(void) |
| { |
| handle_irq(0x82, ioapic_isr_82); |
| ioapic_set_redir(0x0e, 0x82, TRIGGER_LEVEL); |
| |
| set_mask(0x0e, true); |
| set_irq_line(0x0e, 1); |
| |
| asm volatile ("nop"); |
| report(g_isr_82 == 0, "masked level interrupt"); |
| |
| set_mask(0x0e, false); |
| |
| asm volatile ("nop"); |
| report(g_isr_82 == 1, "unmasked level interrupt"); |
| } |
| |
| static volatile int g_isr_83; |
| |
| static void ioapic_isr_83(isr_regs_t *regs) |
| { |
| ++g_isr_83; |
| set_mask(0x0e, true); |
| eoi(); |
| } |
| |
| static void test_ioapic_level_retrigger_mask(void) |
| { |
| handle_irq(0x83, ioapic_isr_83); |
| ioapic_set_redir(0x0e, 0x83, TRIGGER_LEVEL); |
| |
| set_irq_line(0x0e, 1); |
| asm volatile ("nop"); |
| set_mask(0x0e, false); |
| asm volatile ("nop"); |
| report(g_isr_83 == 2, "retriggered level interrupts with mask"); |
| |
| set_irq_line(0x0e, 0); |
| set_mask(0x0e, false); |
| } |
| |
| static volatile int g_isr_84; |
| |
| static void ioapic_isr_84(isr_regs_t *regs) |
| { |
| int line = 0xe; |
| ioapic_redir_entry_t e; |
| |
| ++g_isr_84; |
| set_irq_line(line, 0); |
| |
| e = ioapic_read_redir(line); |
| e.dest_id = 1; |
| |
| // Update only upper part of the register because we only change the |
| // destination, which resides in the upper part |
| ioapic_write_reg(0x10 + line * 2 + 1, ((u32 *)&e)[1]); |
| |
| eoi(); |
| } |
| |
| static void test_ioapic_self_reconfigure(void) |
| { |
| ioapic_redir_entry_t e = { |
| .vector = 0x84, |
| .delivery_mode = 0, |
| .dest_mode = 0, |
| .dest_id = 0, |
| .trig_mode = TRIGGER_LEVEL, |
| }; |
| |
| handle_irq(0x84, ioapic_isr_84); |
| ioapic_write_redir(0xe, e); |
| set_irq_line(0x0e, 1); |
| e = ioapic_read_redir(0xe); |
| report(g_isr_84 == 1 && e.remote_irr == 0, "Reconfigure self"); |
| poll_remote_irr(0xe); |
| } |
| |
| static volatile int g_isr_85; |
| |
| static void ioapic_isr_85(isr_regs_t *regs) |
| { |
| ++g_isr_85; |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_physical_destination_mode(void) |
| { |
| ioapic_redir_entry_t e = { |
| .vector = 0x85, |
| .delivery_mode = 0, |
| .dest_mode = 0, |
| .dest_id = 0x1, |
| .trig_mode = TRIGGER_LEVEL, |
| }; |
| handle_irq(0x85, ioapic_isr_85); |
| ioapic_write_redir(0xe, e); |
| set_irq_line(0x0e, 1); |
| do { |
| pause(); |
| } while(g_isr_85 != 1); |
| report(g_isr_85 == 1, "ioapic physical destination mode"); |
| poll_remote_irr(0xe); |
| } |
| |
| static volatile int g_isr_86; |
| struct spinlock ioapic_lock; |
| |
| static void ioapic_isr_86(isr_regs_t *regs) |
| { |
| spin_lock(&ioapic_lock); |
| ++g_isr_86; |
| spin_unlock(&ioapic_lock); |
| set_irq_line(0x0e, 0); |
| eoi(); |
| } |
| |
| static void test_ioapic_logical_destination_mode(void) |
| { |
| /* Number of vcpus which are configured/set in dest_id */ |
| int nr_vcpus = 3; |
| ioapic_redir_entry_t e = { |
| .vector = 0x86, |
| .delivery_mode = 0, |
| .dest_mode = 1, |
| .dest_id = 0xd, |
| .trig_mode = TRIGGER_LEVEL, |
| }; |
| handle_irq(0x86, ioapic_isr_86); |
| ioapic_write_redir(0xe, e); |
| set_irq_line(0x0e, 1); |
| do { |
| pause(); |
| } while(g_isr_86 < nr_vcpus); |
| report(g_isr_86 == nr_vcpus, "ioapic logical destination mode"); |
| poll_remote_irr(0xe); |
| } |
| |
| int main(void) |
| { |
| setup_vm(); |
| |
| on_cpus(update_cr3, (void *)read_cr3()); |
| mask_pic_interrupts(); |
| |
| if (enable_x2apic()) |
| printf("x2apic enabled\n"); |
| else |
| printf("x2apic not detected\n"); |
| |
| sti(); |
| |
| ioapic_reg_version(); |
| ioapic_reg_id(); |
| ioapic_arbitration_id(); |
| |
| test_ioapic_edge_intr(); |
| test_ioapic_level_intr(); |
| test_ioapic_simultaneous(); |
| |
| test_ioapic_level_coalesce(); |
| test_ioapic_level_sequential(); |
| test_ioapic_level_retrigger(); |
| |
| test_ioapic_edge_mask(); |
| test_ioapic_level_mask(); |
| test_ioapic_level_retrigger_mask(); |
| |
| test_ioapic_edge_tmr(false); |
| test_ioapic_level_tmr(false); |
| test_ioapic_level_tmr(true); |
| test_ioapic_edge_tmr(true); |
| |
| if (cpu_count() > 1) |
| test_ioapic_physical_destination_mode(); |
| if (cpu_count() > 3) |
| test_ioapic_logical_destination_mode(); |
| |
| if (cpu_count() > 1) { |
| test_ioapic_edge_tmr_smp(false); |
| test_ioapic_level_tmr_smp(false); |
| test_ioapic_level_tmr_smp(true); |
| test_ioapic_edge_tmr_smp(true); |
| |
| test_ioapic_self_reconfigure(); |
| } |
| |
| return report_summary(); |
| } |