422 lines
10 KiB
C
422 lines
10 KiB
C
#include "libcflat.h"
|
|
#include "processor.h"
|
|
#include "vm.h"
|
|
#include "desc.h"
|
|
#include "isr.h"
|
|
#include "apic.h"
|
|
#include "apic-defs.h"
|
|
|
|
#ifdef __x86_64__
|
|
# define R "r"
|
|
#else
|
|
# define R "e"
|
|
#endif
|
|
|
|
static inline void io_delay(void)
|
|
{
|
|
}
|
|
|
|
void apic_self_ipi(u8 v)
|
|
{
|
|
apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED |
|
|
APIC_INT_ASSERT | v, 0);
|
|
}
|
|
|
|
void apic_self_nmi(void)
|
|
{
|
|
apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0);
|
|
}
|
|
|
|
#define flush_phys_addr(__s) outl(__s, 0xe4)
|
|
#define flush_stack() do { \
|
|
int __l; \
|
|
flush_phys_addr(virt_to_phys(&__l)); \
|
|
} while (0)
|
|
|
|
extern char isr_iret_ip[];
|
|
|
|
static void flush_idt_page()
|
|
{
|
|
struct descriptor_table_ptr ptr;
|
|
sidt(&ptr);
|
|
flush_phys_addr(virt_to_phys((void*)ptr.base));
|
|
}
|
|
|
|
static volatile unsigned int test_divider;
|
|
static volatile int test_count;
|
|
|
|
ulong stack_phys;
|
|
void *stack_va;
|
|
|
|
void do_pf_tss(void)
|
|
{
|
|
printf("PF running\n");
|
|
install_pte(phys_to_virt(read_cr3()), 1, stack_va,
|
|
stack_phys | PT_PRESENT_MASK | PT_WRITABLE_MASK, 0);
|
|
invlpg(stack_va);
|
|
}
|
|
|
|
extern void pf_tss(void);
|
|
|
|
asm ("pf_tss: \n\t"
|
|
#ifdef __x86_64__
|
|
// no task on x86_64, save/restore caller-save regs
|
|
"push %rax; push %rcx; push %rdx; push %rsi; push %rdi\n"
|
|
"push %r8; push %r9; push %r10; push %r11\n"
|
|
#endif
|
|
"call do_pf_tss \n\t"
|
|
#ifdef __x86_64__
|
|
"pop %r11; pop %r10; pop %r9; pop %r8\n"
|
|
"pop %rdi; pop %rsi; pop %rdx; pop %rcx; pop %rax\n"
|
|
#endif
|
|
"add $"S", %"R "sp\n\t" // discard error code
|
|
"iret"W" \n\t"
|
|
"jmp pf_tss\n\t"
|
|
);
|
|
|
|
|
|
#ifndef __x86_64__
|
|
static void of_isr(struct ex_regs *r)
|
|
{
|
|
printf("OF isr running\n");
|
|
test_count++;
|
|
}
|
|
#endif
|
|
|
|
static void np_isr(struct ex_regs *r)
|
|
{
|
|
printf("NP isr running %lx err=%lx\n", r->rip, r->error_code);
|
|
set_idt_sel(33, read_cs());
|
|
test_count++;
|
|
}
|
|
|
|
static void de_isr(struct ex_regs *r)
|
|
{
|
|
printf("DE isr running divider is %d\n", test_divider);
|
|
test_divider = 10;
|
|
}
|
|
|
|
static void bp_isr(struct ex_regs *r)
|
|
{
|
|
printf("BP isr running\n");
|
|
test_count++;
|
|
}
|
|
|
|
static void nested_nmi_isr(struct ex_regs *r)
|
|
{
|
|
printf("Nested NMI isr running rip=%lx\n", r->rip);
|
|
|
|
if (r->rip != (ulong)&isr_iret_ip)
|
|
test_count++;
|
|
}
|
|
static void nmi_isr(struct ex_regs *r)
|
|
{
|
|
printf("NMI isr running %p\n", &isr_iret_ip);
|
|
test_count++;
|
|
handle_exception(2, nested_nmi_isr);
|
|
printf("Sending nested NMI to self\n");
|
|
apic_self_nmi();
|
|
io_delay();
|
|
printf("After nested NMI to self\n");
|
|
}
|
|
|
|
unsigned long *iret_stack;
|
|
|
|
static void nested_nmi_iret_isr(struct ex_regs *r)
|
|
{
|
|
printf("Nested NMI isr running rip=%lx\n", r->rip);
|
|
|
|
if (r->rip == iret_stack[-3])
|
|
test_count++;
|
|
}
|
|
|
|
extern void do_iret(ulong phys_stack, void *virt_stack);
|
|
|
|
// Return to same privilege level won't pop SS or SP, so
|
|
// save it in RDX while we run on the nested stack
|
|
|
|
asm("do_iret:"
|
|
#ifdef __x86_64__
|
|
"mov %rdi, %rax \n\t" // phys_stack
|
|
"mov %rsi, %rdx \n\t" // virt_stack
|
|
#else
|
|
"mov 4(%esp), %eax \n\t" // phys_stack
|
|
"mov 8(%esp), %edx \n\t" // virt_stack
|
|
#endif
|
|
"xchg %"R "dx, %"R "sp \n\t" // point to new stack
|
|
"pushf"W" \n\t"
|
|
"mov %cs, %ecx \n\t"
|
|
"push"W" %"R "cx \n\t"
|
|
"push"W" $1f \n\t"
|
|
"outl %eax, $0xe4 \n\t" // flush page
|
|
"iret"W" \n\t"
|
|
"1: xchg %"R "dx, %"R "sp \n\t" // point to old stack
|
|
"ret\n\t"
|
|
);
|
|
|
|
static void nmi_iret_isr(struct ex_regs *r)
|
|
{
|
|
unsigned long *s = alloc_page();
|
|
test_count++;
|
|
printf("NMI isr running stack %p\n", s);
|
|
handle_exception(2, nested_nmi_iret_isr);
|
|
printf("Sending nested NMI to self\n");
|
|
apic_self_nmi();
|
|
printf("After nested NMI to self\n");
|
|
iret_stack = &s[128];
|
|
do_iret(virt_to_phys(s), iret_stack);
|
|
printf("After iret\n");
|
|
}
|
|
|
|
static void tirq0(isr_regs_t *r)
|
|
{
|
|
printf("irq0 running\n");
|
|
if (test_count == 1)
|
|
test_count++;
|
|
eoi();
|
|
}
|
|
|
|
static void tirq1(isr_regs_t *r)
|
|
{
|
|
printf("irq1 running\n");
|
|
test_count++;
|
|
eoi();
|
|
}
|
|
|
|
ulong saved_stack;
|
|
|
|
#define switch_stack(S) do { \
|
|
asm volatile ("mov %%" R "sp, %0":"=r"(saved_stack)); \
|
|
asm volatile ("mov %0, %%" R "sp"::"r"(S)); \
|
|
} while(0)
|
|
|
|
#define restore_stack() do { \
|
|
asm volatile ("mov %0, %%" R "sp"::"r"(saved_stack)); \
|
|
} while(0)
|
|
|
|
int main()
|
|
{
|
|
unsigned int res;
|
|
ulong *pt, *cr3, i;
|
|
|
|
setup_vm();
|
|
setup_idt();
|
|
setup_alt_stack();
|
|
|
|
handle_irq(32, tirq0);
|
|
handle_irq(33, tirq1);
|
|
|
|
/* generate HW exception that will fault on IDT and stack */
|
|
handle_exception(0, de_isr);
|
|
printf("Try to divide by 0\n");
|
|
flush_idt_page();
|
|
flush_stack();
|
|
asm volatile ("divl %3": "=a"(res)
|
|
: "d"(0), "a"(1500), "m"(test_divider));
|
|
printf("Result is %d\n", res);
|
|
report("DE exception", res == 150);
|
|
|
|
/* generate soft exception (BP) that will fault on IDT and stack */
|
|
test_count = 0;
|
|
handle_exception(3, bp_isr);
|
|
printf("Try int 3\n");
|
|
flush_idt_page();
|
|
flush_stack();
|
|
asm volatile ("int $3");
|
|
printf("After int 3\n");
|
|
report("BP exception", test_count == 1);
|
|
|
|
#ifndef __x86_64__
|
|
/* generate soft exception (OF) that will fault on IDT */
|
|
test_count = 0;
|
|
handle_exception(4, of_isr);
|
|
flush_idt_page();
|
|
printf("Try into\n");
|
|
asm volatile ("addb $127, %b0\ninto"::"a"(127));
|
|
printf("After into\n");
|
|
report("OF exception", test_count == 1);
|
|
|
|
/* generate soft exception (OF) using two bit instruction that will
|
|
fault on IDT */
|
|
test_count = 0;
|
|
handle_exception(4, of_isr);
|
|
flush_idt_page();
|
|
printf("Try into\n");
|
|
asm volatile ("addb $127, %b0\naddr16 into"::"a"(127));
|
|
printf("After into\n");
|
|
report("2 byte OF exception", test_count == 1);
|
|
#endif
|
|
|
|
/* generate HW interrupt that will fault on IDT */
|
|
test_count = 0;
|
|
flush_idt_page();
|
|
printf("Sending vec 33 to self\n");
|
|
irq_enable();
|
|
apic_self_ipi(33);
|
|
io_delay();
|
|
irq_disable();
|
|
printf("After vec 33 to self\n");
|
|
report("vec 33", test_count == 1);
|
|
|
|
/* generate soft interrupt that will fault on IDT and stack */
|
|
test_count = 0;
|
|
flush_idt_page();
|
|
printf("Try int $33\n");
|
|
flush_stack();
|
|
asm volatile ("int $33");
|
|
printf("After int $33\n");
|
|
report("int $33", test_count == 1);
|
|
|
|
/* Inject two HW interrupt than open iterrupt windows. Both interrupt
|
|
will fault on IDT access */
|
|
test_count = 0;
|
|
flush_idt_page();
|
|
printf("Sending vec 32 and 33 to self\n");
|
|
apic_self_ipi(32);
|
|
apic_self_ipi(33);
|
|
io_delay();
|
|
irq_enable();
|
|
asm volatile("nop");
|
|
irq_disable();
|
|
printf("After vec 32 and 33 to self\n");
|
|
report("vec 32/33", test_count == 2);
|
|
|
|
|
|
/* Inject HW interrupt, do sti and than (while in irq shadow) inject
|
|
soft interrupt. Fault during soft interrupt. Soft interrup shoud be
|
|
handled before HW interrupt */
|
|
test_count = 0;
|
|
flush_idt_page();
|
|
printf("Sending vec 32 and int $33\n");
|
|
apic_self_ipi(32);
|
|
flush_stack();
|
|
io_delay();
|
|
asm volatile ("sti; int $33");
|
|
irq_disable();
|
|
printf("After vec 32 and int $33\n");
|
|
report("vec 32/int $33", test_count == 2);
|
|
|
|
/* test that TPR is honored */
|
|
test_count = 0;
|
|
handle_irq(62, tirq1);
|
|
flush_idt_page();
|
|
printf("Sending vec 33 and 62 and mask one with TPR\n");
|
|
apic_write(APIC_TASKPRI, 0xf << 4);
|
|
irq_enable();
|
|
apic_self_ipi(32);
|
|
apic_self_ipi(62);
|
|
io_delay();
|
|
apic_write(APIC_TASKPRI, 0x2 << 4);
|
|
printf("After 33/62 TPR test\n");
|
|
report("TPR", test_count == 1);
|
|
apic_write(APIC_TASKPRI, 0x0);
|
|
while(test_count != 2); /* wait for second irq */
|
|
irq_disable();
|
|
|
|
/* test fault durint NP delivery */
|
|
printf("Before NP test\n");
|
|
test_count = 0;
|
|
handle_exception(11, np_isr);
|
|
set_idt_sel(33, NP_SEL);
|
|
flush_idt_page();
|
|
flush_stack();
|
|
asm volatile ("int $33");
|
|
printf("After int33\n");
|
|
report("NP exception", test_count == 2);
|
|
|
|
/* generate NMI that will fault on IDT */
|
|
test_count = 0;
|
|
handle_exception(2, nmi_isr);
|
|
flush_idt_page();
|
|
printf("Sending NMI to self\n");
|
|
apic_self_nmi();
|
|
printf("After NMI to self\n");
|
|
/* this is needed on VMX without NMI window notification.
|
|
Interrupt windows is used instead, so let pending NMI
|
|
to be injected */
|
|
irq_enable();
|
|
asm volatile ("nop");
|
|
irq_disable();
|
|
report("NMI", test_count == 2);
|
|
|
|
/* generate NMI that will fault on IRET */
|
|
printf("Before NMI IRET test\n");
|
|
test_count = 0;
|
|
handle_exception(2, nmi_iret_isr);
|
|
printf("Sending NMI to self\n");
|
|
apic_self_nmi();
|
|
/* this is needed on VMX without NMI window notification.
|
|
Interrupt windows is used instead, so let pending NMI
|
|
to be injected */
|
|
irq_enable();
|
|
asm volatile ("nop");
|
|
irq_disable();
|
|
printf("After NMI to self\n");
|
|
report("NMI", test_count == 2);
|
|
stack_phys = (ulong)virt_to_phys(alloc_page());
|
|
stack_va = alloc_vpage();
|
|
|
|
/* Generate DE and PF exceptions serially */
|
|
test_divider = 0;
|
|
set_intr_alt_stack(14, pf_tss);
|
|
handle_exception(0, de_isr);
|
|
printf("Try to divide by 0\n");
|
|
/* install read only pte */
|
|
install_pte(phys_to_virt(read_cr3()), 1, stack_va,
|
|
stack_phys | PT_PRESENT_MASK, 0);
|
|
invlpg(stack_va);
|
|
flush_phys_addr(stack_phys);
|
|
switch_stack(stack_va + 4095);
|
|
flush_idt_page();
|
|
asm volatile ("divl %3": "=a"(res)
|
|
: "d"(0), "a"(1500), "m"(test_divider));
|
|
restore_stack();
|
|
printf("Result is %d\n", res);
|
|
report("DE PF exceptions", res == 150);
|
|
|
|
/* Generate NP and PF exceptions serially */
|
|
printf("Before NP test\n");
|
|
test_count = 0;
|
|
set_intr_alt_stack(14, pf_tss);
|
|
handle_exception(11, np_isr);
|
|
set_idt_sel(33, NP_SEL);
|
|
/* install read only pte */
|
|
install_pte(phys_to_virt(read_cr3()), 1, stack_va,
|
|
stack_phys | PT_PRESENT_MASK, 0);
|
|
invlpg(stack_va);
|
|
flush_idt_page();
|
|
flush_phys_addr(stack_phys);
|
|
switch_stack(stack_va + 4095);
|
|
asm volatile ("int $33");
|
|
restore_stack();
|
|
printf("After int33\n");
|
|
report("NP PF exceptions", test_count == 2);
|
|
|
|
pt = alloc_page();
|
|
cr3 = (void*)read_cr3();
|
|
memset(pt, 0, 4096);
|
|
/* use shadowed stack during interrupt delivery */
|
|
for (i = 0; i < 4096/sizeof(ulong); i++) {
|
|
if (!cr3[i]) {
|
|
cr3[i] = virt_to_phys(pt) | PT_PRESENT_MASK | PT_WRITABLE_MASK;
|
|
pt[0] = virt_to_phys(pt) | PT_PRESENT_MASK | PT_WRITABLE_MASK;
|
|
#ifndef __x86_64__
|
|
((ulong*)(i<<22))[1] = 0;
|
|
#else
|
|
((ulong*)(i<<39))[1] = 0;
|
|
#endif
|
|
write_cr3(virt_to_phys(cr3));
|
|
break;
|
|
}
|
|
}
|
|
test_count = 0;
|
|
printf("Try int 33 with shadowed stack\n");
|
|
switch_stack(((char*)pt) + 4095);
|
|
asm volatile("int $33");
|
|
restore_stack();
|
|
printf("After int 33 with shadowed stack\n");
|
|
report("int 33 with shadowed stack", test_count == 1);
|
|
|
|
return report_summary();
|
|
}
|