paulbergmann_mpstubs/machine/lapic_ipi.cc

229 lines
8.1 KiB
C++

#include "machine/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