forked from BSB-WS23/mpstubs
229 lines
8.1 KiB
C++
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
|