I am writing a small OS-agnostic hypervisor as a teaching tool for my students. The hypervisor code is loaded by the code I embed in a custom MBR on the boot device when the system boots. The hypervisor code switches to 32-bit proected mode and then IA32e (64-bit mode). It then sets up the hypevisor, EPT to protect the hypervisor from the guests, and launches a 16-bit "unrestricted" big real-mode (or unreal mode) guest. All this is working perfectly. The guest can make BIOS calls. The hypervisor writes directly to the video buffer in order to provide debugging/status info. The hypervisor is setup to VMexit minimally (e.g., I/O, APIC, MSRs, etc. are not monitored -- yet). When the real-mode guest causes EPT violations, issues CPUID, etc. these cause VMExits as expected and the hypervisor handles them and resumes the guests.
When the 16-bit guest issues an INIT IPI to itself using the APIC, I run into an infinite VMExit situation that my hypervisor cannot seem to recover from. Essentially, the behaviour is similar to when you IRET from a hardware interrupt handler without acknowledging the interrupt causing the IRQ to trigger again (and again...). In response to the VMExit with a reason of 3 (which is expected), the hypervisor resets the 16-bit guest's registers, limits, access rights, etc. to simulate starting execution from a known initialization point. However, it seems that as soon as the hypervisor resumes guest execution, the VMExit occurs again, repeatedly. On subsequent VMExits with reason 3, the guest RIP and CS base address fields in the VMCS have the reset values that the hypevisor's VMExit INIT handler had setup in the previous VMExit -- the guest code is not getting a chance to resume execution.
Does anyone have any idea as to how my hypervisor can "acknowledge" the INIT IPI to the APIC so that the guest can resume execution? I have tried the global disable bit in the APIC base MSR, but that did not seem to help. I know I can virtualize access to the APIC in order to catch the guest's access to the APIC command register, but is there a simpler way to handle this before I undertake the APIC virtualizaiton effort?