StrikerX3 / OpenXBOX

An experimental (Original) Xbox emulator
79 stars 6 forks source link

Emulate hardware interrupts #4

Closed StrikerX3 closed 6 years ago

StrikerX3 commented 6 years ago

Unicorn (the only CPU emulator used on OpenXBOX) doesn't handle interrupts on its own. We'll need to emulate them while leaving the possibility of taking advantage of interrupt emulation features of more capable CPU emulators.

OSDev's page on Interrupts and the Interrupt Descriptor Table are good starting points for research. The Intel® 64 and IA-32 Architectures Software Developer Manuals are a great resource for understanding interrupts in depth.

This will serve as the basis for emulating certain pieces of Xbox hardware such as the System Clock that plays a role in thread switching implemented on #1.

StrikerX3 commented 6 years ago

I came up with this high-level approach to hardware interrupts in the emulator:

  1. An emulated piece of hardware sends an interrupt signal with a given vector to the Cpu object.
  2. If interrupts are disabled or if that particular interrupt vector is masked, do nothing.
  3. Otherwise, determine whether we are running the CPU emulator or host code.
    • If the CPU emulator is running:
      1. Set the pending interrupt signal to the supplied hardware interrupt vector
      2. Stop the CPU
      3. On the CPU emulation thread, the emulation call in Cpu::RunImpl / Cpu::StepImpl exits
      4. Cpu::Run() / Cpu::Step notices that CPU emulation was interrupted and that there is a pending interrupt request. It clears the pending interrupt and returns with EXIT_INTERRUPT and the interrupt vector
      5. The main CPU emulation loop in Xbox::RunCpu invokes the interrupt handler
    • If the CPU emulator is stopped (we are running host code):
      1. Suspend the CPU emulation thread
      2. Invoke the interrupt handler corresponding to the supplied hardware interrupt vector
      3. Resume the CPU emulation thread

We must guarantee thread safety because interrupts will be performed by other threads. In particular, the pending interrupt request variable and the CPU emulation state must be synchronized. The CPU emulator must not be allowed to run while the interrupt handler is running, and vice-versa. Also, only one interrupt handler may execute at any given point in time. This correlates to the fact that the interrupt handlers are executed by the CPU and we cannot have the same logical thread perform two tasks simultaneously.

The interrupt handler must be aware of the three types of interrupts that work in different ways: interrupt gates, trap gates and task gates. (There are more types, but the Xbox only uses these three in 32-bit mode.) These are set up in the IDT, which is not done by OpenXBOX at the moment. Before invoking the actual handler function, the interrupt handler must prepare the CPU context, which may involve pushing registers onto the stack or even switching tasks using the TSS, then invoke the handler function, and finally complete the interrupt invocation according to the gate type.

StrikerX3 commented 6 years ago

As it turns out, trying to suspend the host thread when it is not running the CPU emulator causes a lot of problems. For instance, threads can be suspended while they are in the middle of printf, in which case, if the interrupt handler thread tries to printf something, it will hang.

Considering that kernel function calls are supposed to be quick, I'm going to handle interrupts exclusively on the Cpu class instead of doing hackery with host threads. That way I can guarantee that the threads aren't doing anything crazy.

StrikerX3 commented 6 years ago

Commit ab4ac27 has the basic interrupt framework implemented. Now we need to setup the IDT, prepare the CPU context and implement the handlers.

As a bonus, we have our first piece of hardware generating interrupts on OpenXBOX: the system clock.

StrikerX3 commented 6 years ago

Commit e710d96 implements the last few details of hardware interrupts, as well as the system clock interrupt handler. With logging enabled, it really slows down emulation, but without logging, it's still pretty fast.