blob: 7d3e37cc81517e5df30620909fbf841613ca6efc [file] [log] [blame]
#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();
}