Commit bd9622ac authored by Christian Dietrich's avatar Christian Dietrich
Browse files

Handout for Assignment 2: Interrupts (I/O APIC)

parent 899542ae
/*! \file
* \brief The \ref Keyboard device handles keystrokes
*/
#pragma once
#include "interrupt/gate.h"
#include "object/key.h"
/*! \brief Handles keystrokes.
* \ingroup io
*
* This class ensures correct initialization of the keyboard and, above all,
* its interrupt handling.
*
*/
class Keyboard : public Gate {
// Prevent copies and assignments
Keyboard(const Keyboard&) = delete;
Keyboard& operator=(const Keyboard&) = delete;
private:
public:
/*! \brief Constructor
*/
Keyboard() {}
/*! \brief Initialization of the keyboard
*
* Initialization of the keyboard and activation of the specific interrupt
* handling: The object will register itself at the \ref Plugbox and
* configure the \ref IOAPIC to receive the corresponding interrupts.
*
* \note The keyboard interrupts should be configured as \ref IOAPIC::LEVEL "level triggered".
* According to the standard we would have to check the corresponding entry in
* \ref ACPI::MADS::Interrupt_Source_Override and use these values. Most likely this would
* suggest an \ref IOAPIC::EDGE "edge-triggered mode" -- which would work as well.
* However, using a \ref IOAPIC::LEVEL "level-triggered mode" is more forgiving because
* it resends the interrupt request even if an interrupt was lost (e.g. the required
* handling, retrieving the buffer entry, was not performed).
*
* \todo Implement Method
*/
void plugin();
/*! \brief Handling of keyboard interrupts
*
* Processes interrupts triggered by the keyboard.
* On each keystroke it outputs the corresponding character on the screen
* -- only \ref Key::valid() "valid keys" are printed, for the sake of
* simplicity all in a separate line dedicated to the keyboard.
* If the key combination \key{Ctrl} + \key{Alt} + \key{Del} is pressed,
* a reboot is triggered.
*
* \todo Implement Method
*/
void trigger() override;
};
// vim: set noet ts=4 sw=4:
// vim: set noet ts=4 sw=4:
/*! \file
* \brief Default interrupt handling device \ref Panic
*/
#pragma once
#include "interrupt/gate.h"
/*! \brief Default handler for (unconfigured) interrupt events
*
* \ref Panic is used to handle unconfigured interrupts and exceptions.
* After a generic error message is displayed, the core is stopped permanently.
*
* During initialization of \ref Plugbox this fake device is assigned for
* all \ref Core::Interrupt::Vector "interrupt vectors"
*/
class Panic : public Gate {
// Prevent copies and assignments
Panic(const Panic&) = delete;
Panic& operator=(const Panic&) = delete;
public:
/*! \brief Constructor
*/
Panic() {}
/*! \brief Simplest possible interrupt handling: Displaying an error message
* and stopping the current core.
*
* \todo Implement Method
*/
void trigger() override;
};
/*! \file
* \brief Class \ref Gate (Device interrupt handling)
*/
#pragma once
/*! \brief Class of objects that are capable of handling interrupts
* \ingroup interrupts
*
* All objects to be assigned in \ref Plugbox must be derived from this class.
*
* Each inheriting class must define the virtual method \ref Gate::trigger().
*/
class Gate {
// Prevent copies and assignments
Gate(const Gate&) = delete;
Gate& operator=(const Gate&) = delete;
public:
/*! \brief Constructor
*/
Gate() {}
/*! \brief Destructor
*
* Classes with virtual methods should always have a virtual destructor
* (which can be empty as well). In \StuBS this will calm the compiler,
* on other systems this will guarantee that delete will free the memory
* for objects of the derived classes correctly.
*/
virtual ~Gate() {}
/*! \brief Device-specific interrupt handler
*
* This method is executed immediately after the interrupt occurs
* (asynchronously).
* Since it is implemented as a pure virtual method, it must be implemented
* by each derived classes.
*
*/
virtual void trigger() = 0;
};
/*! \file
* \brief \ref Plugbox allows assigning \ref Gate "devices" to \ref Core::Interrupt::Vector "interrupt vectors"
*/
#pragma once
#include "machine/core_interrupt.h"
#include "interrupt/gate.h"
/*! \brief Object-oriented abstraction of an device interrupt table
* \ingroup interrupts
*
* This allows you to specify the device handler for each hardware and software
* interrupt and processor exception. Each device source is represented by a
* \ref Gate object. These are located in an array with 256 elements, using
* the index as the vector number.
*/
namespace Plugbox {
/*! \brief Register a \ref Gate object to handle a specific interrupt.
*
* \param vector Interrupt vector handled by the handler routine
* \param gate Object with the handler routine
*
* \todo Implement Method
*/
void assign(Core::Interrupt::Vector vector, Gate *gate);
/*! \brief Query the \ref Gate object for a specific interrupt
*
* \param vector Interrupt vector number
* \return Reference to the registered \ref Gate object
*
* \todo Implement Method
*/
Gate* report(Core::Interrupt::Vector vector);
}; // namespace Plugbox
/*! \file
* \brief Helper for cache alignment
*/
#pragma once
#include "debug/assert.h"
// Helper for aligning to cache line (to prevent false sharing)
#ifndef CACHE_LINE_SIZE
#define CACHE_LINE_SIZE 64
#endif
#define cache_aligned alignas(CACHE_LINE_SIZE)
/*!
* \def assert_cache_aligned(TYPE)
* \brief Compile time check of cache alignment
* \param TYPE data type to check
*/
#define assert_cache_aligned(TYPE) \
static_assert(sizeof(TYPE) % CACHE_LINE_SIZE == 0, STRINGIFY(TYPE) "Not aligned on cache boundary")
#include "ioapic.h"
#include "machine/apic.h"
#include "machine/core.h"
#include "debug/assert.h"
namespace IOAPIC {
/*! \brief IOAPIC registers memory mapped into the CPU's address space.
*
* Access to the actual IOAPIC registers can be obtained by performing the following steps:
* 1. Write the number of the IOAPIC register to the address stored in `IOREGSEL_REG`
* 2. Read the value from / write the value to the address referred to by `IOWIN_REG`.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=8)
*/
volatile Index *IOREGSEL_REG = reinterpret_cast<volatile Index*>(0xfec00000);
/// \copydoc IOREGSEL_REG
volatile Register *IOWIN_REG = reinterpret_cast<volatile Register*>(0xfec00010);
// IOAPIC manual, p. 8
const Index IOAPICID_IDX = 0x00;
const Index IOREDTBL_IDX = 0x10;
const uint8_t slot_max = 24;
} // namespace IOAPIC
/*! \file
* \brief \ref IOAPIC abstracts the access to the I/O \ref APIC
*/
#pragma once
#include "types.h"
#include "machine/ioapic_registers.h"
#include "machine/core_interrupt.h"
/*! \brief Abstraction of the I/O APIC that is used for management of external interrupts.
* \ingroup interrupts
*
* The I/O APIC's Core component is the IO-redirection table. This table is used to configure a flexible mapping between
* the interrupt number and the external interruption. Entries within this table have a width of 64 bit.
* For convenience, the union \ref IOAPIC::RedirectionTableEntry should be used for modifying these tables (see
* file `ioapic_registers.h` for details).
*/
namespace IOAPIC {
/*! \brief Initializes the I/O APIC.
*
* This function will initialize the I/O APIC by initializing the IO-redirection table with sane default values.
* The default interrupt-vector number is chosen such that, in case the interrupt is issued, the panic handler
* is executed. In the beginning, all external interrupts are disabled within the I/O APIC.
* Apart from the redirection table, the `APICID` (read from the system description tables during boot) needs to
* be written to the `IOAPICID` register (see \ref APIC::getIOAPICID() )
*
* \todo Implement Function
*/
void init();
/*! \brief Creates a mapping between an interrupt vector and an external interrupt.
*
* \param slot Number of the slot (i.e., the external interrupt) to configure.
* \param vector Number of the interrupt vector that will be issued for the external interrupt.
* \param trigger_mode Edge or level triggered interrupt signaling (level-triggered interrupts required for the
optional serial interface)
* \param polarity Polarity of the interrupt signaling (active high or active low)
*
* \todo Implement Function
*/
void config(uint8_t slot, Core::Interrupt::Vector vector, TriggerMode trigger_mode = TriggerMode::EDGE,
Polarity polarity = Polarity::HIGH);
/*! \brief Enables the redirection of particular external interrupts to the CPU(s).
*
* To fully enable interrupt handling, the interrupts must be enabled for every CPU (e.g., by calling
* \ref Core::Interrupt::enable() in main)
*
* \param slot Number of the external interrupt that should be enabled.
*
* \todo Implement Function
*/
void allow(uint8_t slot);
/*! \brief Selectively masks external interrupts by slot number.
* \param slot Slot number of the interrupt to be disabled.
*
* \todo Implement Function
*/
void forbid(uint8_t slot);
/*! \brief Check whether an external interrupt source is masked.
* \param slot Slot number of the interrupt to be checked.
* \return Returns `true` iff the interrupt is unmasked, `false` otherwise
*
* \todo Implement Function
*/
bool status(uint8_t slot);
} // namespace IOAPIC
/*! \file
* \brief Helper structures for interacting with the \ref IOAPIC "I/O APIC".
*/
#pragma once
namespace IOAPIC {
typedef uint32_t Index;
typedef uint32_t Register;
extern volatile Index *IOREGSEL_REG;
extern volatile Register *IOWIN_REG;
/*! \brief I/O APIC Identification
*
* The IOAPICID register is register number 0x0. The I/O APIC's ID will be read from the system configuration
* tables (provided by the BIOS) during boot. The number can be queried by calling \ref APIC::getIOAPICID().
* During initialization, this number must be written to the IOAPICID register.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=9), page 9
*/
union Identification {
struct {
uint32_t : 24, ///< Reserved, do not modify
id : 4, ///< I/O APIC Identification
: 4; ///< Reserved, do not modify
};
Register value;
explicit Identification(Register value) : value(value) {}
} __attribute__((packed));
static_assert(sizeof(Identification) == 4, "IOAPIC Identification has wrong size");
/*! \brief Delivery mode specifies the type of interrupt sent to the CPU. */
enum DeliveryMode {
FIXED = 0, ///< "ordinary" interrupt; send to ALL cores listed in the destination bit mask
LOWEST_PRIORITY = 1, ///< "ordinary" interrupt; send to the lowest priority core from destination mask
SMI = 2, ///< System Management Interrupt; vector number required to be 0
// Reserved
NMI = 4, ///< Non-Maskable Interrupt, vector number ignored, only edge triggered
INIT = 5, ///< Initialization interrupt (always treated as edge triggered)
// Reserved
EXTERN_INT = 7 ///< external interrupt (only edge triggered)
};
/*! \brief Way of interpreting the value written to the destination field. */
enum DestinationMode {
PHYSICAL = 0, ///< Destination contains the physical destination APIC ID
LOGICAL = 1 ///< Destination contains a mask of logical APIC IDs
};
/*! \brief Interrupt polarity for the redirection-table entry */
enum Polarity {
HIGH = 0, ///< active high
LOW = 1 ///< active low
};
/*! \brief Trigger mode */
enum TriggerMode {
EDGE = 0, ///< edge triggered
LEVEL = 1 ///< level triggered
};
/*! \brief Interrupt state */
enum DeliveryStatus {
IDLE = 0, ///< No activity for this interrupt
SEND_PENDING = 1 ///< Interrupt will be sent as soon as the bus / LAPIC is ready
};
/*! \brief Interrupt masking */
enum InterruptMask {
UNMASKED = 0, ///< Redirection-table entry is active (non-masked)
MASKED = 1 ///< Redirection-table entry is inactive (masked)
};
/*! \brief Entry in the redirection table.
*
* The redirection table begins with I/O APIC register `0x10` and ends at `0x3f`.
*
* Each entry has a size of 64 bit, equaling two I/O APIC registers.
* For instance, entry 0 is stored in registers `0x10` and `0x11`, in which the low-order 32 bit
* (equals \ref value_low) and high-order 32 bit (equals \ref value_high) need to be stored.
*
* The union defined below provides an overlay allowing convenient modification of individual
* bits, while the 32-bit values \ref value_low and \ref value_high can be used for writing to
* the I/O APIC registers.
*
* \note [Type punning](https://en.wikipedia.org/wiki/Type_punning#Use_of_union)
* is indeed undefined behavior in C++. However, *gcc* explicitly allows this construct as a
* [language extension](https://gcc.gnu.org/bugs/#nonbugs).
* Some compilers ([other than gcc](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Type%2Dpunning)
* might allow this feature only by disabling strict aliasing (`-fno-strict-aliasing`).
* In \StuBS we use this feature extensively due to the improved code readability.
*
* \see [IO-APIC manual](intel_ioapic.pdf#page=11), page 11-13
*/
union RedirectionTableEntry {
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table (IDT)" will be
* activated when the corresponding external interrupt triggers.
*/
uint64_t vector : 8;
/*! \brief The delivery mode denotes the way the interrupts will be delivered to the local CPU
* cores, respectively to their local APICs.
*
* For StuBS, we use \ref LOWEST_PRIORITY, as all CPU cores have the same
* priority and we want to distribute interrupts evenly among them.
* It, however, is not guaranteed that this method of load balancing will work on every system.
*/
DeliveryMode delivery_mode : 3;
/*! \brief The destination mode defines how the value stored in \ref destination will be
* interpreted.
*
* For StuBS, we use \ref LOGICAL
*/
DestinationMode destination_mode : 1;
/*! \brief Delivery status holds the current status of interrupt delivery.
*
* \note This field is read only; write accesses to this field will be ignored.
*/
DeliveryStatus delivery_status : 1;
/*! \brief The polarity denotes when an interrupt should be issued.
*
* For StuBS, we usually use \ref HIGH (i.e., when the interrupt line is, logically, `1`).
*/
Polarity polarity : 1;
/*! \brief The remote IRR bit indicates whether the local APIC(s) accept the level interrupt.
*
* Once the LAPIC sends an \ref LAPIC::endOfInterrupt "End Of Interrupt (EOI)",
* this bit is reset to `0`.
*
* \note This field is read only and is only meaningful for level-triggered interrupts.
*/
uint64_t remote_irr : 1;
/*! \brief The trigger mode states whether the interrupt signaling is level or edge triggered.
*
* StuBS uses \ref EDGE for the Timer, the Keybaord and (optional) serial interface
* need \ref LEVEL
*/
TriggerMode trigger_mode : 1;
/*! \brief Mask or unmask interrupts for a particular, external source.
*
* The interrupt mask denotes whether interrupts should be accepted/unmasked
* (value \ref UNMASKED) or ignored/masked (value \ref MASKED).
*/
InterruptMask interrupt_mask : 1;
/*! \brief Reserved, do not modify. */
uint64_t : 39;
/*! \brief Interrupt destination.
*
* The meaning of destination depends on the destination mode:
* For the logical destination mode, destination holds a bit mask made up of the cores that
* are candidates for receiving the interrupt.
* In the single-core case, this value is `1`, in the multi-core case, the `n` low-order bits
* needs to be set (with `n` being the number of CPU cores, see \ref Core::count() ).
* Setting the `n` low-order bits marks all available cores as candidates for receiving
* interrupts and thereby balancing the number of interrupts between the cores.
*
* \note This form of load balancing depends on the hardware's behavior and may not work on all
* systems in the same fashion. Most notably, in QEMU all interrupts are sent to the BSP
* (core 0).
*/
uint64_t destination : 8;
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
// @cond ANONYMOUS_STRUCT
struct {
// @endcond
Register value_low; ///< Low-order 32 bits (for the register with the smaller index)
Register value_high; ///< High-order 32 bits (for the register with the higher index)
// @cond ANONYMOUS_STRUCT
} __attribute__((packed));
// @endcond
/*! \brief Constructor for an redirection-table entry
*
* Every entry in the redirection table represents an external source of interrupts and has a size
* of 64 bits. Due to the I/O APIC registers being only 32 bits wide, the constructor takes two
* 32 bit values.
*
* \param value_low First, low-order 32 bit value
* \param value_high Second, high-order 32 bit value
*/
RedirectionTableEntry(Register value_low, Register value_high) : value_low(value_low), value_high(value_high) {}
};
static_assert(sizeof(RedirectionTableEntry) == 8, "IOAPIC::RedirectionTableEntry has wrong size");
} // namespace IOAPIC
/*! \file
* \brief Contains the class Spinlock
*/
#pragma once
#include "machine/core.h"
#include "machine/cache.h"
/*! \brief By the use of Spinlocks, it is possible to serialize blocks of code
* that might run parallel on multiple CPU cores.
*
* \ingroup sync
*
* Synchronization is implemented using a lock variable. Once a thread enters
* the critical area, it sets the lock variable (to a non-zero value); when
* this thread leaves the critical area, it resets the lock variable to zero.
* When trying to enter an already locked critical area, the trying thread
* actively waits until the critical area is free again.
*
* Use the following two GCC intrinsics
* - `bool __atomic_test_and_set(void *ptr, int memorder)`
* - `void __atomic_clear (bool *ptr, int memorder)`
*
* These intrinsics are translated into atomic, architecture-specific
* CPU instructions.
*
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
* This is not the most efficient memory order but works reasonably well.
*
* <a href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic Builtins in GCC manual</a>
*/
class Spinlock {
// Prevent copies and assignments
Spinlock(const Spinlock& copy) = delete;
Spinlock& operator=(const Spinlock&) = delete;
public:
/*! \brief Constructor; Initializes as unlocked.
*
* \todo Complete Constructor (for \OOStuBS, or use \ref Ticketlock)
*
*/
Spinlock() {}
/*! \brief Enters the critical area. In case the area is already locked,
* \ref lock() will actively wait for the area can be entered.
*
* \see \ref Core::pause()
* \todo Implement Method (for \OOStuBS, or use \ref Ticketlock)
*/
void lock() {
}
/*! \brief Unblocks the critical area.
*
*
* \todo Implement Method (for \OOStuBS, or use \ref Ticketlock)
*/
void unlock() {
}
};
/*! \file
* \brief Contains the class Ticketlock
*/
#pragma once
#include "machine/core.h"
#include "machine/cache.h"
/*! \brief By the use of Ticketlocks, it is possible to serialize blocks of code
* that might run parallel on multiple CPU cores.
*
* \ingroup sync
*
* Synchronization is implemented using a lock and a ticket variable.
* Once a thread tries to enter the critical area, it obtains a ticket by
* atomic increment of the ticket variable and waits until the lock variable
* is equal to its ticket.
* When a thread leaves the critical area, it increments the lock variable by
* one and thereby allows the next thread to enter the critical area.
*
* If you want that things just work, choose __ATOMIC_SEQ_CST as memorder.
* This is not the most efficient memory order but works reasonably well.
*
* <a href="https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html">Atomic Builtins in GCC manual</a>
*/
class Ticketlock {
// Prevent copies and assignments
Ticketlock(const Ticketlock& copy) = delete;
Ticketlock& operator=(const Ticketlock&) = delete;
public:
/*! \brief Constructor
*
* \todo Complete Constructor (for \MPStuBS)
*/
Ticketlock() {}
/*! \brief Enters the critical area. In case the area is already locked,
* \ref lock() will actively wait for the area can be entered.
*
* \see \ref Core::pause()
* \todo Implement Method (for \MPStuBS)
*/
void lock() {
}
/*! \brief Unblocks the critical area.
*
* \todo Implement Method (for \MPStuBS)
*/
void unlock() {
}
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment