Aufgabe 3: Pro-/Epilog
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.
Learning Objectives
- Handling of asynchronous events
- Problems and protection of critical sections
Videos (WS21, in German)
- Interrupts und Traps auf x86 (16 min)
- Externe Interrupts (5 min)
- Remotedebugging mit GDB (2 min)
- Aufgabe 2 (6 min)
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 (or Ticketlock) 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)
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).
- Note
- Older versions of QEMU had a bug in the handling of concurrent PS/2 events. If you witness erroneous behaviour, which you cannot trace back to your code, see our FAQ entry.
Hence you have to extend the PS2Controller with:
- the initialization routine to enable the mouse and configure it to send interrupts on events (PS2Controller::MOUSE_STREAMING_ENABLE)
- data retrieval of the packets sent by the mouse (PS2Controller::fetch(Pointer&))
- decoding the packets (similar to KeyDecoder)
- adjusting the data retrieval of the keyboard (PS2Controller::fetch(Key&)) to not drop mouse packets
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.