Handout
This commit is contained in:
244
arch/lapic_ipi.cc
Normal file
244
arch/lapic_ipi.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "lapic_registers.h"
|
||||
|
||||
namespace LAPIC {
|
||||
namespace IPI {
|
||||
|
||||
/*! \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)
|
||||
INIT_LEVEL_DEASSERT = 5, ///< Synchronization interrupt
|
||||
STARTUP = 6, ///< Dedicated Startup-Interrupt (SIPI)
|
||||
// Reserved
|
||||
};
|
||||
|
||||
/*! \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 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 level */
|
||||
enum Level {
|
||||
DEASSERT = 0, ///< Must be zero when DeliveryMode::INIT_LEVEL_DEASSERT
|
||||
ASSERT = 1 ///< Must be one for all other delivery modes
|
||||
};
|
||||
|
||||
/*! \brief Trigger mode for DeliveryMode::INIT_LEVEL_DEASSERT */
|
||||
enum TriggerMode {
|
||||
EDGE_TRIGGERED = 0, ///< edge triggered
|
||||
LEVEL_TRIGGERED = 1 ///< level triggered
|
||||
};
|
||||
|
||||
/*! \brief Shorthand for commonly used destinations */
|
||||
enum DestinationShorthand {
|
||||
NO_SHORTHAND = 0, ///< Use destination field instead of shorthand
|
||||
SELF = 1, ///< Send IPI to self
|
||||
ALL_INCLUDING_SELF = 2, ///< Send IPI to all including self
|
||||
ALL_EXCLUDING_SELF = 3 ///< Send IPI to all except self
|
||||
};
|
||||
|
||||
/*! \brief Interrupt mask */
|
||||
enum InterruptMask {
|
||||
UNMASKED = 0, ///< Interrupt entry is active (non-masked)
|
||||
MASKED = 1 ///< Interrupt entry is deactivated (masked)
|
||||
};
|
||||
|
||||
/*! \brief Interrupt Command
|
||||
*
|
||||
* \see [ISDMv3 10.6.1 Interrupt Command Register
|
||||
* (ICR)](intel_manual_vol3.pdf#page=381)
|
||||
*/
|
||||
union InterruptCommand {
|
||||
struct {
|
||||
/*! \brief Interrupt vector in the \ref IDT "Interrupt Descriptor Table
|
||||
* (IDT)" will be activated when the corresponding external interrupt
|
||||
* triggers.
|
||||
*//*! \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 `DeliveryMode::LowestPriority`, 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.
|
||||
*/
|
||||
enum DeliveryMode delivery_mode : 3;
|
||||
|
||||
/*! \brief The destination mode defines how the value stored in
|
||||
* `destination` will be interpreted.
|
||||
*
|
||||
* For StuBS, we use `DestinationMode::Logical`.
|
||||
*/
|
||||
enum 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.
|
||||
*/
|
||||
enum DeliveryStatus delivery_status : 1;
|
||||
|
||||
uint64_t : 1; ///< reserved
|
||||
|
||||
/*! \brief The polarity denotes when an interrupt should be issued.
|
||||
*
|
||||
* For StuBS, we use `Polarity::High` (i.e., when the interrupt line is,
|
||||
* logically, 1).
|
||||
*/
|
||||
enum Level level : 1;
|
||||
|
||||
/*! \brief The trigger mode states whether the interrupt signaling is level
|
||||
* or edge triggered.
|
||||
*
|
||||
* StuBS uses `TriggerMode::Edge` for Keyboard and Timer, the (optional)
|
||||
* serial interface, however, needs `TriggerMode::Level`.
|
||||
*/
|
||||
enum TriggerMode trigger_mode : 1;
|
||||
|
||||
uint64_t : 2; ///< reserved
|
||||
|
||||
enum DestinationShorthand destination_shorthand : 2;
|
||||
|
||||
uint64_t : 36; ///< Reserved, do not modify
|
||||
|
||||
/*! \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;
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief I/O 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 64-bit value is split in two 32 bit values.
|
||||
*/
|
||||
struct {
|
||||
Register value_low; ///< First, low-order register
|
||||
Register value_high; ///< Second, high-order register
|
||||
} __attribute__((packed));
|
||||
|
||||
/*! \brief Default constructor */
|
||||
InterruptCommand() = default;
|
||||
|
||||
explicit InterruptCommand(
|
||||
uint8_t destination, uint8_t vector = 0,
|
||||
DestinationMode destination_mode = DestinationMode::PHYSICAL,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->destination_mode = destination_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = DestinationShorthand::NO_SHORTHAND;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
InterruptCommand(DestinationShorthand destination_shorthand, uint8_t vector,
|
||||
DeliveryMode delivery_mode = DeliveryMode::FIXED,
|
||||
TriggerMode trigger_mode = TriggerMode::EDGE_TRIGGERED,
|
||||
Level level = Level::ASSERT) {
|
||||
readRegister();
|
||||
this->vector = vector;
|
||||
this->delivery_mode = delivery_mode;
|
||||
this->level = level;
|
||||
this->trigger_mode = trigger_mode;
|
||||
this->destination_shorthand = destination_shorthand;
|
||||
this->destination = destination;
|
||||
}
|
||||
|
||||
void send() const {
|
||||
write(INTERRUPT_COMMAND_REGISTER_HIGH, value_high);
|
||||
write(INTERRUPT_COMMAND_REGISTER_LOW, value_low);
|
||||
}
|
||||
|
||||
bool isSendPending() {
|
||||
value_low = read(INTERRUPT_COMMAND_REGISTER_LOW);
|
||||
return delivery_status == DeliveryStatus::SEND_PENDING;
|
||||
}
|
||||
|
||||
private:
|
||||
void readRegister() {
|
||||
while (isSendPending()) {
|
||||
}
|
||||
value_high = read(INTERRUPT_COMMAND_REGISTER_HIGH);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(InterruptCommand) == 8,
|
||||
"LAPIC Interrupt Command has wrong size");
|
||||
|
||||
bool isDelivered() {
|
||||
InterruptCommand ic;
|
||||
return !ic.isSendPending();
|
||||
}
|
||||
|
||||
void send(uint8_t destination, uint8_t vector) {
|
||||
InterruptCommand ic(destination, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendGroup(uint8_t logical_destination, uint8_t vector) {
|
||||
InterruptCommand ic(logical_destination, vector, DestinationMode::LOGICAL);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendAll(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_INCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendOthers(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendInit(bool assert) {
|
||||
LAPIC::IPI::InterruptCommand ic(
|
||||
DestinationShorthand::ALL_EXCLUDING_SELF, 0, DeliveryMode::INIT,
|
||||
assert ? TriggerMode::EDGE_TRIGGERED : TriggerMode::LEVEL_TRIGGERED,
|
||||
assert ? Level::ASSERT : Level::DEASSERT);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
void sendStartup(uint8_t vector) {
|
||||
InterruptCommand ic(DestinationShorthand::ALL_EXCLUDING_SELF, vector,
|
||||
DeliveryMode::STARTUP);
|
||||
ic.send();
|
||||
}
|
||||
|
||||
} // namespace IPI
|
||||
} // namespace LAPIC
|
||||
Reference in New Issue
Block a user