Page 1 of 1

APIC timer doesn't work in QEMU

Posted: Mon Jan 12, 2015 4:04 am
by shine
Hi,
I'm developing a 64-bit OS for studying, now I have problem with APIC timer.
When I was testing APIC timer in QEMU, I found that APIC doesn't generate any interrupts. I also tested the same code in VMware Workstation, and things are going very well there.
Here's a part of my APIC implementation:

Code: Select all

void *apicbase;
u64 apicread(u32 reg)
{
#ifdef ENABLE_X2APIC
	return readmsr(0x800+reg);
#else
	return *((volatile u64*)(apicbase+reg*0x10));
#endif
}

void apicwrite(u32 reg,u64 val)
{
#ifdef ENABLE_X2APIC
	writemsr(0x800+reg,val);
#else
	*((volatile u64*)(apicbase+reg*0x10))=val;
#endif
}

void sendeoi()
{
	apicwrite(APIC_REG_EOI,0);
}

void int_spurious(u16 num,u16 ss,u64 rsp,u64 rflags,u16 cs,u64 rip,u64 errorcode)
{
	sendeoi();
}

void int_timer(u16 num,u16 ss,u64 rsp,u64 rflags,u16 cs,u64 rip,u64 errorcode)
{
	puts("int");
	sendeoi();
}

u8 initapic()
{
	struct apic_lvt lvt;

#ifndef ENABLE_X2APIC
	apicbase=kcalloc_align(1,4096,4096);	//void* kcalloc_align(size_t num,size_t size,size_t align)
	if(!apicbase)return 0;
	u64 *basepage=getpageentry(apicbase);
	*basepage=((*basepage)&(~0xfff))|0b11011;	//mark this page as uncachable
	invlpaging();

	memcpy(apicbase,0xfee00000,4096);

	apicwrite(APIC_REG_DFR,0xffffffff);
	apicwrite(APIC_REG_LDR, (apicread(APIC_REG_LDR)&0xffffff)|1);
	memset(&lvt,0,sizeof(lvt));
	lvt.mask=1;
	apicwrite(APIC_REG_LVT_TMR,*((u32*)(&lvt)));
	apicwrite(APIC_REG_LVT_PERF,*((u32*)(&lvt)));
	apicwrite(APIC_REG_LVT_LINT0,*((u32*)(&lvt)));
	apicwrite(APIC_REG_LVT_LINT1,*((u32*)(&lvt)));
	apicwrite(APIC_REG_TASKPRIOR,0);
#endif

	struct apic_base base;
	*((u64*)(&base))=readmsr(APIC_BASE_MSR);
#ifdef ENABLE_X2APIC
	base.enable_x2apic=base.enable_global=1;
#else
	base.enable_global=1;base.enable_x2apic=0;
	base.base=(size_t)apicbase>>12;
#endif
	writemsr(APIC_BASE_MSR,*((u64*)(&base)));

	if(!registerinterrupt(0x7f,int_spurious,0))
		return 0;

	struct apic_spurious spurious;
	memset(&spurious,0,sizeof(spurious));
	spurious.enable=1;
	spurious.vector=0x7f;
	apicwrite(APIC_REG_SPURIOUS,*((u32*)(&spurious)));

	if(!registerinterrupt(0x81,int_timer,0))
		return 0;

	enable_int();

	memset(&lvt,0,sizeof(lvt));
	lvt.vector=0x81;
	lvt.dmode=APIC_LVT_DMODE_FIXED;
	lvt.trigger=APIC_LVT_TRIGGER_EDGE;
	lvt.mask=0;
	lvt.timer=APIC_LVT_TIMER_PERIODIC;
	apicwrite(APIC_REG_LVT_TMR,*((u32*)(&lvt)));

	apicwrite(APIC_REG_TMRDIV,0x10);
	apicwrite(APIC_REG_TMRINITCNT,0x10);

	return 1;
}
If everything goes well, it should put lots of "int" on the screen. But in QEMU it does nothing at all.
I am supporting xAPIC and x2APIC at the same time, in QEMU it works in none of these modes while in VMware Workstation it works in both.

I tried
-cpu qemu64,+apic,+x2apic
-cpu Haswell,+apic,+x2apic
for QEMU in different versions(1.8.0, 2.0.0 and 2.2.0), but I still can't solve this problem.

If I've made any mistakes please let me know, thanks.