1
Files
interrupt-handling-using-th…/chap/listings.tex
2023-03-02 17:29:30 +01:00

378 lines
16 KiB
TeX

\chapter{Listings}
\label{ch:listings}
This chapter contains concrete implementation examples for concepts mentioned in \autoref{ch:implementation}, demonstrated on the example of hhuOS\@.
The code is simplified and not necessarily identical to the actual implementation, to allow for a self-contained demonstration.
Names and namespaces were adjusted especially.
\clearpage
\section{Integration into HhuOS}
\label{sec:apxhhuos}
\subsection{Interrupt Handling in HhuOS}
\label{subsec:apxcurrenthhuos}
Devices that provide interrupts implement the \code{InterruptHandler} interface:
\begin{codeblock}{The InterruptHandler Interface (InterruptHandler.h)~\autocite{hhuos}}{C++}
\cppfile{code/interrupthandler_def.cpp}
\end{codeblock}
These interrupt handlers have to be registered to the appropriate interrupt vector:
\begin{codeblock}{Assigning an Interrupt Handler (InterruptDispatcher.cpp)~\autocite{hhuos}}{C++}
\cppfile{code/interruptdispatcher_assign.cpp}
\end{codeblock}
Multiple interrupt handlers can be registered to the same interrupt vector.
For every interrupt that arrives at a CPU, the first-stage interrupt handler (registered in the IDT for every vector) is called, which in turn calls the device-specific handlers:
\begin{codeblock}{Triggering an Interrupt Handler (InterruptDispatcher.cpp)~\autocite{hhuos}}{C++}
\cppfile{code/interruptdispatcher_dispatch.cpp}
\end{codeblock}
The PIT interrupt handler manages the system time and the scheduler:
\begin{codeblock}{The PIT Interrupt Handler (Pit.cpp)~\autocite{hhuos}}{C++}
\cppfile{code/pit_trigger.cpp}
\end{codeblock}
\clearpage
\subsection{Necessary Modifications to HhuOS}
\label{subsec:hhuosintegration}
To integrate the APIC implementation into hhuOS, some preexisting components of its interrupt infrastructure (see \autoref{sec:apxhhuos}) have to be modified:
\begin{enumerate}
\item The \code{InterruptService} has to forward calls to the \code{Apic} class instead of the \code{Pic} class, depending on if the APIC system is enabled -- to keep the option of running the OS on hardware that does not support the APIC (see \autoref{lst:interruptserviceafter}).
\item The \code{Pit} interrupt handler may no longer trigger the scheduler preemption if the APIC timer is enabled (see \autoref{lst:pithandlerafter}).
\item The \code{System::initializeSystem} function needs to enable the APIC system if it is available (see \autoref{lst:systemafter}).
\item The existing devices using interrupts need to use the new \code{InterruptVector} and \code{InterruptRequest} enums.
\end{enumerate}
\begin{codeblock}[label=lst:interruptserviceafter]{Using the Correct Interrupt Controller (InterruptService.cpp)}{C++}
\cppfile{code/interruptservice_after.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:pithandlerafter]{Disabling PIT Preemption (Pit.cpp)}{C++}
\cppfile{code/pit_after.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:systemafter]{Enabling the APIC System (System.cpp)}{C++}
\cppfile{code/system_after.cpp}
\end{codeblock}
\subsection{Public Interface of the APIC Subsystem}
\label{subsec:hhuospublicinterface}
To interact with the APIC subsystem from the \textquote{outside}, the \code{Apic} class is used, which exposes the necessary functionality:
\begin{codeblock}{The APIC Public Interface (Apic.h)}{C++}
\cppfile{code/apic_def.cpp}
\end{codeblock}
Enabling the APIC subsystem is done by the \code{enable} function (see \autoref{fig:apicenable}).
\clearpage
\section{Local APIC}
\label{sec:apxlocalapic}
\subsection{Accessing Local APIC Registers in xApic Mode}
\label{subsec:apxxapicregacc}
The xApic register access requires a single page of strong uncachable memory, but since this requires setting attributes in the \textquote{Page Attribute Table}\footnote{
See \url{https://docs.kernel.org/x86/pat.html} (visited on 12/02/2023).},
\ this implementation only uses hhuOS' \code{mapIO} function, which maps a physical address to the kernel heap, with hhuOS' \textquote{CACHE\textunderscore{}DISABLE} flag set in the kernel page table:
\begin{codeblock}{Allocating the Local APIC's MMIO Region (LocalApic.cpp)}{C++}
\cppfile{code/lapic_mmio_alloc.cpp}
\end{codeblock}
A register can now be written as follows:
\begin{codeblock}[label=lst:lapicmmiowrite]{Writing a Local APIC MMIO Register (LocalApic.cpp)}{C++}
\cppfile{code/lapic_mmio_write.cpp}
\end{codeblock}
To reduce the usage of manual bit-shifting and -masking, this implementation provides structures for some commonly used registers, that implement conversion operators to the register format:
\begin{codeblock}[label=lst:msrentry]{The MSREntry Structure (LocalApicRegisters.h)}{C++}
\cppfile{code/lapic_msr_entry.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:svrentry]{The SVREntry Structure (LocalApicRegisters.h)}{C++}
\cppfile{code/lapic_svr_entry.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:lvtentry]{The LVTEntry Structure (LocalApicRegisters.h)}{C++}
\cppfile{code/lapic_lvt_entry.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:icrentry]{The ICREntry Structure (LocalApicRegisters.h)}{C++}
\cppfile{code/lapic_icr_entry.cpp}
\end{codeblock}
These can be used in combination with some convenience functions:
\begin{codeblock}{Writing the IA32\textunderscore{}APIC\textunderscore{}BASE MSR (LocalApic.cpp)}{C++}
\cppfile{code/lapic_write_msr.cpp}
\end{codeblock}
\begin{codeblock}{Writing the SVR (LocalApic.cpp)}{C++}
\cppfile{code/lapic_write_svr.cpp}
\end{codeblock}
\begin{codeblock}{Writing the LVT (LocalApic.cpp)}{C++}
\cppfile{code/lapic_write_lvt.cpp}
\end{codeblock}
\begin{codeblock}{Writing the ICR (LocalApic.cpp)}{C++}
\cppfile{code/lapic_write_icr.cpp}
\end{codeblock}
\subsection{Disabling PIC Mode}
\label{subsec:apxdisablepic}
Setting the IMCR\footnote{
Writing the IMCR is detailed in the \textquote{MultiProcessor Specification}~\autocite[sec.~3.6.2.1]{mpspec}.}
\ using hhuOS' \code{IoPort} class:
\begin{codeblock}{Disabling PIC Mode (LocalApic.cpp)}{C++}
\cppfile{code/lapic_imcr.cpp}
\end{codeblock}
\subsection{Initializing the LVT}
\label{subsec:apxlvtinit}
The interrupt vectors are set as defined by the \code{InterruptVector} enum.
This implementation configures the LVT by using the \code{LVTEntry} struct (see \autoref{lst:lvtentry}):
\begin{codeblock}{Configuring the LINT0 Local Interrupt (LocalApic.cpp)}{C++}
\cppfile{code/lapic_lvt_example.cpp}
\end{codeblock}
This process is repeated for each local interrupt.
\subsection{Handling the Spurious Interrupt}
\label{subsec:apxsvr}
This implementation sets the SVR by using the \code{SVREntry} struct (see \autoref{lst:svrentry}):
\begin{codeblock}{Setting the Spurious Interrupt Vector (LocalApic.cpp)}{C++}
\cppfile{code/lapic_svr_example.cpp}
\end{codeblock}
Because hhuOS uses a two-stage interrupt handling approach (described in \autoref{sec:currenthhuos}), the spurious interrupt does not receive its own interrupt handler.
Instead, it is ignored in the \code{dispatch} function, hhuOS' \textquote{first-stage} interrupt handler:
\begin{codeblock}{Checking for Spurious Interrupts (InterruptDispatcher.cpp)}{C++}
\cppfile{code/interruptdispatcher_check_spurious.cpp}
\end{codeblock}
\begin{codeblock}{Ignoring Spurious Interrupts (InterruptDispatcher.cpp)}{C++}
\cppfile{code/interruptdispatcher_ignore_spurious.cpp}
\end{codeblock}
\subsection{Using the APIC Timer}
\label{subsec:apxapictimer}
The timer frequency is determined by counting the ticks in one millisecond, using the PIT as calibration source:
\begin{codeblock}{Calibrating the APIC Timer (ApicTimer.cpp)}{C++}
\cppfile{code/apictimer_calibrate.cpp}
\end{codeblock}
This calibration is performed before the interrupts get enabled, so it is not possible to use hhuOS' \code{TimeService} for the delay.
Instead, the \code{PIT::earlyDelay} function is used, which configures the PIT for mode \textquote{Interrupt on Terminal Count} on channel zero and polls the channel's output status~\autocite{pit}:
\begin{codeblock}{Microsecond Delay without Interrupts (Pit.cpp)}{C++}
\cppfile{code/pit_early_delay.cpp}
\end{codeblock}
Furthermore, the calibration is only performed once, even if multiple APIC timers are used.
This removes the possibility of using multiple timers with different dividers.
To handle the APIC timer interrupt on multiple cores, \(n\) \code{ApicTimer} instances are registered to the appropriate interrupt vector, where \(n\) is the number of CPUs.
Because this means, that each APIC timer interrupt on any CPU core triggers all \(n\) interrupt handlers, the handler has to determine if it belongs to the calling CPU\@.
The handler belonging to the BSP's APIC timer also triggers the scheduler preemption (instead of the PIT):
\begin{codeblock}{Handling the APIC Timer Interrupt (ApicTimer.cpp)}{C++}
\cppfile{code/apictimer_trigger.cpp}
\end{codeblock}
\subsection{Handling Local APIC Errors}
\label{subsec:apxhandlingerror}
The error interrupt handler obtains the ESR's contents by writing to it first:
\begin{codeblock}{The Local APIC Error Interrupt Handler (ApicErrorHandler.cpp)}{C++}
\cppfile{code/apicerror_trigger.cpp}
\end{codeblock}
Because every CPU core can only access its own local APIC's registers, a single instance of this interrupt handler can be used for each AP in the system.
\clearpage
\section{I/O APIC}
\label{sec:apxioapic}
\subsection{Accessing I/O APIC Registers}
\label{subsec:iolistings}
The I/O APIC's indirect register access requires two MMIO registers, that can be written similar to the local APIC's registers:
\begin{codeblock}{Writing an I/O APIC MMIO Register (IoApic.h)}{C++}
\cppfile{code/ioapic_write_mmio.cpp}
\end{codeblock}
Using the \textquote{Index} and \textquote{Data} registers to access the I/O APIC's indirect registers:
\begin{codeblock}{Writing an I/O APIC Indirect Register (IoApic.cpp)}{C++}
\cppfile{code/ioapic_write_indirect.cpp}
\end{codeblock}
To reduce the manual bit-shifting and -masking, the same approach is used as for the local APIC\@:
\begin{codeblock}[label=lst:redtblentry]{The REDTBLEntry Structure (IoApicRegisters.h)}{C++}
\cppfile{code/ioapic_redtbl_entry.cpp}
\end{codeblock}
\begin{codeblock}[label=lst:writeredtbl]{Writing the REDTBL (IoApic.cpp)}{C++}
\cppfile{code/ioapic_write_redtbl.cpp}
\end{codeblock}
\subsection{Interrupt Overrides}
\label{subsec:apxirqoverrides}
This implementation represents an interrupt override using the following structure:
\begin{codeblock}{The External Interrupt Override Structure (IoApic.h)}{C++}
\cppfile{code/ioapic_irqoverride.cpp}
\end{codeblock}
During initialization, the overrides are used to set the correct interrupt vectors, polarities and trigger modes for each REDTBL entry.
The \code{REDTBLEntry} struct (see \autoref{lst:redtblentry}) is used to write the REDTBL\@:
\begin{codeblock}{Initializing the REDTBL (IoApic.cpp)}{C++}
\cppfile{code/ioapic_redtbl_example.cpp}
\end{codeblock}
During regular operation, the overrides are used to determine the correct REDTBL entries for e.g.\ unmasking an interrupt:
\begin{codeblock}{Unmasking an IRQ (Apic.cpp)}{C++}
\cppfile{code/apic_allow.cpp}
\end{codeblock}
To convey this deviation between GSIs and PC/AT compatible IRQs very clearly, the public \code{Apic::allow} function accepts an \code{InterruptRequest} as an argument (that allows addressing the interrupt by name), while the internal \code{IoApic::allow} function only accepts a \code{GlobalSystemInterrupt} as argument:
\begin{codeblock}{Unmasking an IRQ Internally (IoApic.cpp)}{C++}
\cppfile{code/ioapic_allow.cpp}
\end{codeblock}
The internal \code{IoApic::allow} function is hidden (\code{private}) from the OS, and gets only called by the exposed \code{Apic::allow} function.
This prevents accidentally setting the wrong REDTBL entry by not taking possible interrupt overrides into account.
The same principle is applied to the other operations concerning GSIs.
\clearpage
\section{Symmetric Multiprocessing}
\label{sec:apxsymmetric}
An overview of the complete SMP startup process can be found in \autoref{fig:smpenable}.
\subsection{Issuing Inter-Processor Interrupts}
\label{subsec:apxipis}
To issue an IPI, the ICR is written by using the \code{ICREntry} struct (see \autoref{lst:icrentry}):
\begin{codeblock}{Issuing an INIT IPI (LocalApic.cpp)}{C++}
\cppfile{code/lapic_initipi_example.cpp}
\end{codeblock}
\begin{codeblock}{Issuing a SIPI (LocalApic.cpp)}{C++}
\cppfile{code/lapic_sipi_example.cpp}
\end{codeblock}
\subsection{Preparing Symmetric Multiprocessing Startup}
\label{subsec:apxpreparesmp}
Before executing the \textquote{Universal Startup Algorithm}, the boot code has to be relocated to physical lower memory.
The memory region used for this copy has to be identity-mapped to the virtual kernel address space, so the effective addresses do not change after enabling paging in protected mode.
To keep the required variables available to the startup code, these are located in the routines \textquote{TEXT} section and initialized during runtime.
This approach was taken from SerenityOS~\autocite[APIC.cpp]{serenity}:
\begin{codeblock}{Preparing the Boot Routine's Variables (ApicSmp.cpp)}{C++}
\cppfile{code/ap_boot_variables.cpp}
\end{codeblock}
Now, the initialized startup routine can be copied to \code{0x8000}:
\begin{codeblock}{Relocating the Boot Routine (ApicSmp.cpp)}{C++}
\cppfile{code/ap_boot_copy.cpp}
\end{codeblock}
The \code{boot\textunderscore{}ap} function is the entry of the startup routine, it is described further in \autoref{subsec:apxapboot}.
\subsection{Universal Startup Algorithm}
\label{subsec:apxmpusa}
The \textquote{INIT-SIPI-SIPI} sequence, or \textquote{Universal Startup Algorithm} is performed by issuing IPIs as described in \autoref{subsec:apxipis} and using the PIT as time source (see \autoref{subsec:apxapictimer}):
\begin{codeblock}{The Universal Startup Algorithm (ApicSmp.cpp)}{C++}
\cppfile{code/ap_boot_usa.cpp}
\end{codeblock}
Boot completion is signaled in the AP's entry function:
\begin{codeblock}{Signaling AP Boot Completion (smp\textunderscore{}entry.cpp)}{C++}
\cppfile{code/smp_entry.cpp}
\end{codeblock}
\subsection{Application Processor Boot Routine}
\label{subsec:apxapboot}
Because like the BSP, every AP is initialized in real mode, it has to be switched to protected mode first:
\begin{codeblock}{Preparing the Switch to Protected Mode (smp\textunderscore{}boot.asm)}{nasm}
\nasmfile{code/ap_boot_real_prepare.asm}
\end{codeblock}
Then, by enabling protected mode, setting the segment registers and far-jumping to the \SI{32}{\bit} code segment, the switch is performed:
\begin{codeblock}{Switching from Real Mode to Protected Mode (smp\textunderscore{}boot.asm)}{nasm}
\nasmfile{code/ap_boot_real.asm}
\end{codeblock}
In \SI{32}{\bit} protected mode, paging is enabled and interrupt processing is prepared by reusing the control register values and the IDT from the BSP\@:
\begin{codeblock}{Reusing Values from the BSP (smp\textunderscore{}boot.asm)}{nasm}
\nasmfile{code/ap_boot_protected_bsp.asm}
\end{codeblock}
Finally, to call the AP entry function, the AP requires its own stack. If hardware context switching is to be used, the AP additionally requires its own GDT and TSS\@:
\begin{codeblock}{Calling the Entry Function (smp\textunderscore{}boot.asm)}{nasm}
\nasmfile{code/ap_boot_protected_ap.asm}
\end{codeblock}
\subsection{Application Processor Post-Boot Routine}
\label{subsec:apxappostboot}
When the AP has booted, it initializes its own APIC, including the APIC timer and error handler:
\begin{codeblock}{Initializing the Core's Local APIC (LocalApic.cpp)}{C++}
\cppfile{code/ap_boot_post.cpp}
\end{codeblock}
Because this added another possible recipient to internal APIC messages, the arbitration IDs are synchronized by issuing an \textquote{INIT-level-deassert IPI} using the \code{ICREntry} struct (see \autoref{lst:icrentry}):
\begin{codeblock}{Synchronizing the Arbitration IDs (LocalApic.cpp)}{C++}
\cppfile{code/lapic_apr_example.cpp}
\end{codeblock}
Because hhuOS' paging is not designed for multiple processors, the booted AP remains in a busy loop and does not enable its interrupts.
\cleardoublepage