StuBS
Assignment 2: Interrupts

StuBS needs simple interrupt handling, e.g. for the keyboard. To configure the interrupts, StuBS will support the Advanced Programmable Interrupt Controller.

A driver for an interrupt-driven device like the Keyboard inherits from Gate, defining the interface. The Plugbox stores the mapping from interrupt vectors to Gate objects and gets queried by the interrupt_handler during interrupt handling.

However, to allow interrupts from external devices you have to configure them in the IOAPIC during initialization. The low level part of the interrupt_handler, including the entry functions, setting up the IDT and LAPIC, as well as helper functions (e.g., to enable/disable interrupts on the current core), are already implemented.

Map of important classes for the second assignment

Learning Objectives

  • Handling of asynchronous events
  • Problems and protection of critical sections

Configuring I/O APIC

Modern PCs usually support xAPIC, having up to 24 external devices connected to the I/O APIC (you can even have multiple I/O APICs – although StuBS only supports the first one), with a local APIC integrated in each core and all connected via the APIC bus.

During the boot process, interrupts are masked for each core and the I/O APIC. Implement the functionality in IOAPIC to configure interrupts from external sources. To test your implementation, you can configure and enable (level triggered) interrupts from the keyboard (using APIC::getIOAPICSlot with APIC::KEYBOARD) – since the interrupt vector table has already been initialized by the startup code, the interrupt_handler() should be executed automatically if a key is pressed or released! However, since the characters are not fetched by the PS2Controller, you will continuously receive the interrupt until you empty the keyboard buffer by reading its content using either fetch() or PS2Controller::drainBuffer().

Depending on the environment, it may also be necessary to empty the keyboard buffer completely before activating interrupts.

Hints

  • During the handling of an interrupt, you don't need to worry about unwanted interrupts. They are disabled on the corresponding core when handling starts, and not enabled until the interrupt_handler() returns (and the interrupt_entry() function exits the handling using the assembler statement iret [interrupt return]).
  • Obviously, interrupt handling will only work if StuBS is running. As soon as it leaves the main() function, it returns to kernel_init(), which disables interrupts and stops the current core by calling Core::die() – an operating system should not suddenly stop 😉. In MPStuBS this also applies to main_ap(), which is executed by the application processors.
  • To make your life easier, we provide an implementation of the Local APIC. In addition, the startup code ensures that it is in a well defined state, hence the interrupt handling should already work if you implement the functions in IOAPIC correctly.
  • According to the specification (see ISDMv3, 10.8.5 Signaling Interrupt Servicing Completion) it is necessary to confirm the processing of each interrupt (EOI). To do this, call LAPIC::endOfInterrupt() in the interrupt_handler.

Further Reading

Managing Devices with the Plugbox

Now you need to make sure that each device driver's interrupt service routine is called once the corresponding interrupt fires. The Plugbox manages those driver objects, providing a pointer to a Gate object for each possible interrupt vector. Gate is an abstract class that describes the interface of all interrupt handling drivers.

By default, the Plugbox shall return a reference to a global Panic object – as the name implies – should cause the kernel to panic if an unknown interrupt fires. This can be achieved by using a global array of references to Gate objects. Each of them is initialized with a reference to the aforementioned global Panic object.

Keyboard Device Driver

Write the Keyboard device driver which implements the Gate interface and hence intercepts and interprets the interrupts triggered by the keyboard. After each keystroke, the corresponding characters should be displayed on the screen at a fixed location. Use kout (using TextStream::setPos()). Safe the cursor position before printing the character, and restore it afterwards. The key combination Ctrl-Alt-Delete should trigger a system reboot.

You can reuse the PS2Controller implementation of assignment 1 to query for the Key, however, you have to adjust the function PS2Controller::fetch(Key&) to the changed situation.

Test Application

Adapt your test program to the interrupt processing as follows:

OOStuBS

Implement a test program in Application::action(), which is called from main(). This should infinitely generate output (for example, an increasing number) at a fixed position of the main output window kout (using TextStream::setPos()).

You should be able to jumble the output by pressing keys. Think about what is happening, why it is happening, and avoid the problem using functions provided by the Core namespace (or its subnamespaces) – while still using the kout object for the output in the interrupt handler.

MPStuBS

Implement a test program in Application::action() similar to OOStuBS, but create an instance for each core which is then called in main() and main_ap() respectively. The output of each instance should be in a distinct but fixed line in kout. On the first execution, you'll see that the multi-core system does not even need interrupts to mess up the output (Think about why that is the case). To avoid this problem the methods provided by Core alone are not enough. Therefore, you have to write a Spinlock in order to synchronize parallel control flows. Think about what must be considered when using locks and what problems can occur.

Remote Debugging (Voluntary)

A remote debugging stub certainly will improve your debugging experience, and StuBS can be extended to support GDB Remote Serial Protocol with reasonable effort – you'll only have to provide the Serial interface (voluntary exercise of assignment 1). The GDB_Stub comes with its own interrupt handler and will modify the IDT in such a way that it handles all traps. Hence, it will stop on each trap, report the issue to the connected GDB host and wait for further instructions.

Note
For this task it is not necessary for Serial to support interrupt mode – this will be implemented in a slightly advanced version in the next assignment.

Mouse Support (Voluntary)

Attention
The PS/2 emulation on our test machine does not work at the moment. Try QEMU, or skip this task.

Tilt your head down slightly, a little to the right (or, if you are left-handed, into the opposite direction). The chances are quite good that you'll find a pointing device (aka mouse) next to your keyboard. And just like the keyboard it can be controlled by the PS2Controller – even if it is a USB device (as long as the USB legacy support is enabled).

Hence you have to extend the PS2Controller with:

A Mouse device driver (similar to Keyboard) should be triggered on interrupts from the pointing device, fetch the position and display a mouse pointer (in software, not to be confused with the CGA hardware cursor!) on the screen.

Further Reading