Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/arch/armv8/inc/arch/vgic.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct vgic_int {
#endif
bool hw;
bool in_lr;
bool in_spilled;
bool enabled;
};

Expand Down
120 changes: 87 additions & 33 deletions src/arch/armv8/vgic.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,48 @@ void vgic_send_sgi_msg(struct vcpu* vcpu, cpumap_t pcpu_mask, irqid_t int_id)
}
}

static void vgic_add_spilled(struct vcpu* vcpu, struct vgic_int* interrupt)
{
spin_lock(&vcpu->vm->arch.vgic_spilled_lock);
if (!interrupt->in_lr && !interrupt->in_spilled) {
struct list* spilled_list = NULL;
if (gic_is_priv(interrupt->id)) {
spilled_list = &vcpu->arch.vgic_spilled;
} else {
spilled_list = &vcpu->vm->arch.vgic_spilled;
}
list_push(spilled_list, (node_t*)interrupt);
interrupt->in_spilled = true;
}
spin_unlock(&vcpu->vm->arch.vgic_spilled_lock);
gich_set_hcr(gich_get_hcr() | GICH_HCR_NPIE_BIT);
}

static void vgic_route(struct vcpu* vcpu, struct vgic_int* interrupt)
{
if ((interrupt->state == INV) || !interrupt->enabled) {
if (interrupt->state == INV) {
return;
}

/**
* A disabled interrupt that is still active must be placed in an LR so
* the guest can EOI it directly, avoiding an unnecessary LRENP maintenance
* interrupt trap.
*/
if (!interrupt->enabled && !(interrupt->state & ACT)) {
return;
}

if (vgic_int_vcpu_is_target(vcpu, interrupt)) {
vgic_add_lr(vcpu, interrupt);
}

if (!interrupt->in_lr && vgic_int_has_other_target(vcpu, interrupt)) {
/**
* An active interrupt cannot be forwarded to another CPU — ownership
* cannot be yielded while the interrupt is active, so any IPI would
* be silently ignored by the recipient.
*/
if (!interrupt->in_lr && !(interrupt->state & ACT) && vgic_int_has_other_target(vcpu, interrupt)) {
struct cpu_msg msg = {
(uint32_t)VGIC_IPI_ID,
VGIC_ROUTE,
Expand All @@ -168,6 +199,15 @@ static void vgic_route(struct vcpu* vcpu, struct vgic_int* interrupt)
}
}
}

/**
* If the interrupt was not placed in an LR or forwarded to another CPU,
* track it in the spilled list so it gets injected when an LR slot
* becomes available.
*/
if (!interrupt->in_lr && !interrupt->in_spilled) {
vgic_add_spilled(vcpu, interrupt);
}
}

static inline void vgic_write_lr(struct vcpu* vcpu, struct vgic_int* interrupt, size_t lr_ind)
Expand Down Expand Up @@ -234,10 +274,17 @@ static inline void vgic_write_lr(struct vcpu* vcpu, struct vgic_int* interrupt,
lr |= GICH_LR_EOI_BIT;
}

lr |= ((gic_lr_t)state << GICH_LR_STATE_OFF) & GICH_LR_STATE_MSK;
/**
* If the interrupt is disabled, strip PEND from the LR so the guest
* only EOIs the active portion and the interrupt is not re-delivered
* while disabled. The PEND bit is preserved in interrupt->state so
* it is restored when the interrupt is re-enabled.
*/
unsigned lr_state = !interrupt->enabled ? (state & (unsigned)~PEND) : state;
lr |= ((gic_lr_t)lr_state << GICH_LR_STATE_OFF) & GICH_LR_STATE_MSK;
}

interrupt->state = (uint8_t)INV;
interrupt->state = !interrupt->enabled ? (uint8_t)(state & PEND) : (uint8_t)INV;
interrupt->in_lr = true;
interrupt->lr = (uint8_t)lr_ind;
vcpu->arch.vgic_priv.curr_lrs[lr_ind] = interrupt->id;
Expand All @@ -261,7 +308,7 @@ bool vgic_remove_lr(struct vcpu* vcpu, struct vgic_int* interrupt)
interrupt->in_lr = false;

if (GICH_LR_STATE(lr_val) != INV) {
interrupt->state = (uint8_t)GICH_LR_STATE(lr_val);
interrupt->state = (uint8_t)(GICH_LR_STATE(lr_val) | (interrupt->state & PEND));
#if (GIC_VERSION == GICV2)
if (interrupt->id < GIC_MAX_SGIS) {
if (interrupt->state & ACT) {
Expand All @@ -283,18 +330,14 @@ bool vgic_remove_lr(struct vcpu* vcpu, struct vgic_int* interrupt)
return ret;
}

static void vgic_add_spilled(struct vcpu* vcpu, struct vgic_int* interrupt)
/* Must be called holding vgic_spilled_lock */
static void vgic_remove_spilled(struct vcpu* vcpu, struct vgic_int* interrupt)
{
spin_lock(&vcpu->vm->arch.vgic_spilled_lock);
struct list* spilled_list = NULL;
if (gic_is_priv(interrupt->id)) {
spilled_list = &vcpu->arch.vgic_spilled;
} else {
spilled_list = &vcpu->vm->arch.vgic_spilled;
}
list_push(spilled_list, (node_t*)interrupt);
spin_unlock(&vcpu->vm->arch.vgic_spilled_lock);
gich_set_hcr(gich_get_hcr() | GICH_HCR_NPIE_BIT);
struct list* spilled_list = gic_is_priv(interrupt->id)
? &vcpu->arch.vgic_spilled
: &vcpu->vm->arch.vgic_spilled;
list_rm(spilled_list, &interrupt->node);
interrupt->in_spilled = false;
}

static void vgic_spill_lr(struct vcpu* vcpu, size_t lr_ind)
Expand All @@ -315,7 +358,7 @@ bool vgic_add_lr(struct vcpu* vcpu, struct vgic_int* interrupt)
{
bool ret = false;

if (!interrupt->enabled || interrupt->in_lr) {
if ((!interrupt->enabled && !(interrupt->state & ACT)) || interrupt->in_lr) {
return ret;
}

Expand Down Expand Up @@ -359,7 +402,7 @@ bool vgic_add_lr(struct vcpu* vcpu, struct vgic_int* interrupt)
}
}

if (pend_found > 1) {
if (pend_found > 1 && pend_ind >= 0) {
lr_ind = pend_ind;
} else {
lr_ind = act_ind;
Expand All @@ -371,6 +414,11 @@ bool vgic_add_lr(struct vcpu* vcpu, struct vgic_int* interrupt)
}

if (lr_ind >= 0) {
if (interrupt->in_spilled) {
spin_lock(&vcpu->vm->arch.vgic_spilled_lock);
vgic_remove_spilled(vcpu, interrupt);
spin_unlock(&vcpu->vm->arch.vgic_spilled_lock);
}
vgic_write_lr(vcpu, interrupt, (size_t)lr_ind);
ret = true;
} else {
Expand Down Expand Up @@ -1054,8 +1102,7 @@ void vgic_ipi_handler(uint32_t event, uint64_t data)
/**
* Must be called holding the vgic_spilled_lock
*/
static inline struct vgic_int* vgic_highest_prio_spilled(struct vcpu* vcpu, unsigned flags,
struct list** outlist)
static inline struct vgic_int* vgic_highest_prio_spilled(struct vcpu* vcpu, unsigned flags)
{
struct vgic_int* irq = NULL;
struct list* spilled_lists[] = {
Expand All @@ -1077,7 +1124,6 @@ static inline struct vgic_int* vgic_highest_prio_spilled(struct vcpu* vcpu, unsi
bool is_lower_id = temp_irq->id < irq_id;
if (is_higher_prio || (is_same_prio && is_lower_id)) {
irq = temp_irq;
*outlist = list;
}
}
}
Expand All @@ -1091,13 +1137,12 @@ static void vgic_refill_lrs(struct vcpu* vcpu, bool npie)
unsigned flags = npie ? PEND : ACT | PEND;
spin_lock(&vcpu->vm->arch.vgic_spilled_lock);
while (lr_ind >= 0) {
struct list* list = NULL;
struct vgic_int* irq = vgic_highest_prio_spilled(vcpu, flags, &list);
struct vgic_int* irq = vgic_highest_prio_spilled(vcpu, flags);
if (irq != NULL) {
spin_lock(&irq->lock);
bool got_ownership = vgic_get_ownership(vcpu, irq);
if (got_ownership) {
list_rm(list, &irq->node);
vgic_remove_spilled(vcpu, irq);
vgic_write_lr(vcpu, irq, (size_t)lr_ind);
}
spin_unlock(&irq->lock);
Expand All @@ -1118,19 +1163,28 @@ static void vgic_refill_lrs(struct vcpu* vcpu, bool npie)

static void vgic_eoir_highest_spilled_active(struct vcpu* vcpu)
{
struct list* list = NULL;
struct vgic_int* interrupt = vgic_highest_prio_spilled(vcpu, ACT, &list);
struct vgic_int* interrupt;

spin_lock(&vcpu->vm->arch.vgic_spilled_lock);
interrupt = vgic_highest_prio_spilled(vcpu, ACT);
if (interrupt != NULL) {
spin_lock(&interrupt->lock);
if (vgic_get_ownership(vcpu, interrupt)) {
interrupt->state &= (uint8_t)~ACT;
if (vgic_int_is_hw(interrupt)) {
gic_set_act(interrupt->id, false);
} else {
if (interrupt->state & PEND) {
vgic_add_lr(vcpu, interrupt);
}
vgic_remove_spilled(vcpu, interrupt);
} else {
spin_unlock(&interrupt->lock);
interrupt = NULL;
}
}
spin_unlock(&vcpu->vm->arch.vgic_spilled_lock);

if (interrupt != NULL) {
interrupt->state &= (uint8_t)~ACT;
if (vgic_int_is_hw(interrupt)) {
gic_set_act(interrupt->id, false);
} else {
if (interrupt->state & PEND) {
vgic_add_lr(vcpu, interrupt);
}
}
spin_unlock(&interrupt->lock);
Expand Down
Loading