@@ -650,6 +650,7 @@ static void gic_update_one_lr(struct vcpu *v, int i)
p = irq_to_pending(v, irq);
if ( lr & GICH_LR_ACTIVE )
{
+ set_bit(GIC_IRQ_GUEST_ACTIVE, &p->status);
if ( test_bit(GIC_IRQ_GUEST_ENABLED, &p->status) &&
test_and_clear_bit(GIC_IRQ_GUEST_QUEUED, &p->status) )
{
@@ -673,6 +674,7 @@ static void gic_update_one_lr(struct vcpu *v, int i)
if ( p->desc != NULL )
p->desc->status &= ~IRQ_INPROGRESS;
clear_bit(GIC_IRQ_GUEST_VISIBLE, &p->status);
+ clear_bit(GIC_IRQ_GUEST_ACTIVE, &p->status);
p->lr = GIC_INVALID_LR;
if ( test_bit(GIC_IRQ_GUEST_ENABLED, &p->status) &&
test_bit(GIC_IRQ_GUEST_QUEUED, &p->status) )
@@ -703,20 +705,55 @@ void gic_clear_lrs(struct vcpu *v)
static void gic_restore_pending_irqs(struct vcpu *v)
{
- int i;
- struct pending_irq *p, *t;
+ int lr = 0, lrs = nr_lrs;
+ struct pending_irq *p, *t, *p_r;
+ struct list_head *inflight_r;
unsigned long flags;
spin_lock_irqsave(&v->arch.vgic.lock, flags);
+
+ if ( list_empty(&v->arch.vgic.lr_pending) )
+ goto out;
+
+ inflight_r = &v->arch.vgic.inflight_irqs;
list_for_each_entry_safe ( p, t, &v->arch.vgic.lr_pending, lr_queue )
{
- i = find_first_zero_bit(&this_cpu(lr_mask), nr_lrs);
- if ( i >= nr_lrs ) return;
+ lr = find_next_zero_bit(&this_cpu(lr_mask), nr_lrs, lr);
+ if ( lr >= nr_lrs )
+ {
+ /* No more free LRs: find a lower priority irq to evict */
+ list_for_each_entry_reverse( p_r, inflight_r, inflight )
+ {
+ if ( p_r->priority == p->priority )
+ goto out;
+ if ( test_bit(GIC_IRQ_GUEST_VISIBLE, &p_r->status) &&
+ !test_bit(GIC_IRQ_GUEST_ACTIVE, &p_r->status) )
+ goto found;
+ }
+ /* We didn't find a victim this time, and we won't next
+ * time, so quit */
+ goto out;
+
+found:
+ lr = p_r->lr;
+ p_r->lr = GIC_INVALID_LR;
+ set_bit(GIC_IRQ_GUEST_QUEUED, &p_r->status);
+ clear_bit(GIC_IRQ_GUEST_VISIBLE, &p_r->status);
+ gic_add_to_lr_pending(v, p_r);
+ inflight_r = &p_r->inflight;
+ }
- gic_set_lr(i, p, GICH_LR_PENDING);
+ gic_set_lr(lr, p, GICH_LR_PENDING);
list_del_init(&p->lr_queue);
- set_bit(i, &this_cpu(lr_mask));
+ set_bit(lr, &this_cpu(lr_mask));
+
+ /* We can only evict nr_lrs entries */
+ lrs--;
+ if ( lrs == 0 )
+ break;
}
+
+out:
spin_unlock_irqrestore(&v->arch.vgic.lock, flags);
}
@@ -733,8 +770,44 @@ void gic_clear_pending_irqs(struct vcpu *v)
int gic_events_need_delivery(void)
{
- return (!list_empty(¤t->arch.vgic.lr_pending) ||
- this_cpu(lr_mask));
+ int mask_priority, lrs = nr_lrs;
+ int max_priority = 0xff, active_priority = 0xff;
+ struct vcpu *v = current;
+ struct pending_irq *p;
+ unsigned long flags;
+
+ mask_priority = (GICH[GICH_VMCR] >> GICH_VMCR_PRIORITY_SHIFT) & GICH_VMCR_PRIORITY_MASK;
+ mask_priority = mask_priority << 3;
+
+ spin_lock_irqsave(&v->arch.vgic.lock, flags);
+
+ /* TODO: We order the guest irqs by priority, but we don't change
+ * the priority of host irqs. */
+ list_for_each_entry( p, &v->arch.vgic.inflight_irqs, inflight )
+ {
+ if ( test_bit(GIC_IRQ_GUEST_ACTIVE, &p->status) )
+ {
+ if ( p->priority < active_priority )
+ active_priority = p->priority;
+ break;
+ } else if ( test_bit(GIC_IRQ_GUEST_ENABLED, &p->status) ) {
+ if ( p->priority < max_priority )
+ max_priority = p->priority;
+ }
+ if ( p->priority >= mask_priority )
+ break;
+ lrs--;
+ if ( lrs == 0 )
+ break;
+ }
+
+ spin_unlock_irqrestore(&v->arch.vgic.lock, flags);
+
+ if ( max_priority < active_priority &&
+ max_priority < mask_priority )
+ return 1;
+ else
+ return 0;
}
void gic_inject(void)
@@ -56,8 +56,9 @@ struct pending_irq
*
*/
#define GIC_IRQ_GUEST_QUEUED 0
-#define GIC_IRQ_GUEST_VISIBLE 1
-#define GIC_IRQ_GUEST_ENABLED 2
+#define GIC_IRQ_GUEST_ACTIVE 1
+#define GIC_IRQ_GUEST_VISIBLE 2
+#define GIC_IRQ_GUEST_ENABLED 3
unsigned long status;
struct irq_desc *desc; /* only set it the irq corresponds to a physical irq */
int irq;
@@ -129,6 +129,9 @@
#define GICH_LR_CPUID_SHIFT 9
#define GICH_VTR_NRLRGS 0x3f
+#define GICH_VMCR_PRIORITY_MASK 0x1f
+#define GICH_VMCR_PRIORITY_SHIFT 27
+
/*
* The minimum GICC_BPR is required to be in the range 0-3. We set
* GICC_BPR to 0 but we must expect that it might be 3. This means we
gic_events_need_delivery should only return positive if an outstanding pending irq has an higher priority than the currently active irq and the priority mask. Introduce GIC_IRQ_GUEST_ACTIVE to track which ones are currently active in the guest. Rewrite the function by going through the priority ordered inflight and lr_queue lists. In gic_restore_pending_irqs replace lower priority pending (and not active) irqs in GICH_LRs with higher priority irqs if no more GICH_LRs are available. Signed-off-by: Stefano Stabellini <stefano.stabellini@eu.citrix.com> --- Changes in v8: - in gic_restore_pending_irqs rename i to lr; - add in code comments in gic_restore_pending_irqs and gic_events_need_delivery; - add << 3 to mask_priority. Changes in v7: - fix locking for the list_empty case in gic_restore_pending_irqs; - add in code comment; - gic_events_need_delivery: break out of the loop as soon as we find the active irq as inflight_irqs is ordered by priority; - gic_events_need_delivery: break out of the loop if p->priority is lower than mask_priority as inflight_irqs is ordered by priority; - use find_next_zero_bit instead of find_first_zero_bit; - in gic_restore_pending_irqs remember the last position of the inner loop search and continue from there; - in gic_restore_pending_irqs use a priority check to get out of the inner loop. Changes in v5: - improve in code comments; - use list_for_each_entry_reverse instead of writing my own list walker. Changes in v4: - in gic_events_need_delivery go through inflight_irqs and only consider enabled irqs. --- xen/arch/arm/gic.c | 89 ++++++++++++++++++++++++++++++++++++++---- xen/include/asm-arm/domain.h | 5 ++- xen/include/asm-arm/gic.h | 3 ++ 3 files changed, 87 insertions(+), 10 deletions(-)