summaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/apic/vector.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/kernel/apic/vector.c')
-rw-r--r--arch/x86/kernel/apic/vector.c288
1 files changed, 116 insertions, 172 deletions
diff --git a/arch/x86/kernel/apic/vector.c b/arch/x86/kernel/apic/vector.c
index a2761740d345..88219b80d9ec 100644
--- a/arch/x86/kernel/apic/vector.c
+++ b/arch/x86/kernel/apic/vector.c
@@ -28,16 +28,15 @@ struct apic_chip_data {
struct irq_cfg cfg;
unsigned int cpu;
unsigned int prev_cpu;
+ unsigned int irq;
struct hlist_node clist;
- cpumask_var_t domain;
- cpumask_var_t old_domain;
u8 move_in_progress : 1;
};
struct irq_domain *x86_vector_domain;
EXPORT_SYMBOL_GPL(x86_vector_domain);
static DEFINE_RAW_SPINLOCK(vector_lock);
-static cpumask_var_t vector_cpumask, vector_searchmask, searched_cpumask;
+static cpumask_var_t vector_searchmask;
static struct irq_chip lapic_controller;
static struct irq_matrix *vector_matrix;
#ifdef CONFIG_SMP
@@ -101,194 +100,124 @@ static struct apic_chip_data *alloc_apic_chip_data(int node)
struct apic_chip_data *apicd;
apicd = kzalloc_node(sizeof(*apicd), GFP_KERNEL, node);
- if (!apicd)
- return NULL;
- if (!zalloc_cpumask_var_node(&apicd->domain, GFP_KERNEL, node))
- goto out_data;
- if (!zalloc_cpumask_var_node(&apicd->old_domain, GFP_KERNEL, node))
- goto out_domain;
- INIT_HLIST_NODE(&apicd->clist);
+ if (apicd)
+ INIT_HLIST_NODE(&apicd->clist);
return apicd;
-out_domain:
- free_cpumask_var(apicd->domain);
-out_data:
- kfree(apicd);
- return NULL;
}
static void free_apic_chip_data(struct apic_chip_data *apicd)
{
- if (apicd) {
- free_cpumask_var(apicd->domain);
- free_cpumask_var(apicd->old_domain);
- kfree(apicd);
- }
+ kfree(apicd);
}
-static int __assign_irq_vector(int irq, struct apic_chip_data *d,
- const struct cpumask *mask,
- struct irq_data *irqd)
+static void apic_update_irq_cfg(struct irq_data *irqd)
{
- /*
- * NOTE! The local APIC isn't very good at handling
- * multiple interrupts at the same interrupt level.
- * As the interrupt level is determined by taking the
- * vector number and shifting that right by 4, we
- * want to spread these out a bit so that they don't
- * all fall in the same interrupt level.
- *
- * Also, we've got to be careful not to trash gate
- * 0x80, because int 0x80 is hm, kind of importantish. ;)
- */
- static int current_vector = FIRST_EXTERNAL_VECTOR + VECTOR_OFFSET_START;
- static int current_offset = VECTOR_OFFSET_START % 16;
- int cpu, vector;
+ struct apic_chip_data *apicd = apic_chip_data(irqd);
- /*
- * If there is still a move in progress or the previous move has not
- * been cleaned up completely, tell the caller to come back later.
- */
- if (d->cfg.old_vector)
- return -EBUSY;
+ lockdep_assert_held(&vector_lock);
- /* Only try and allocate irqs on cpus that are present */
- cpumask_clear(d->old_domain);
- cpumask_clear(searched_cpumask);
- cpu = cpumask_first_and(mask, cpu_online_mask);
- while (cpu < nr_cpu_ids) {
- int new_cpu, offset;
+ apicd->cfg.dest_apicid = apic->calc_dest_apicid(apicd->cpu);
+ irq_data_update_effective_affinity(irqd, cpumask_of(apicd->cpu));
+ trace_vector_config(irqd->irq, apicd->cfg.vector, apicd->cpu,
+ apicd->cfg.dest_apicid);
+}
- cpumask_copy(vector_cpumask, cpumask_of(cpu));
+static void apic_update_vector(struct irq_data *irqd, unsigned int newvec,
+ unsigned int newcpu)
+{
+ struct apic_chip_data *apicd = apic_chip_data(irqd);
+ struct irq_desc *desc = irq_data_to_desc(irqd);
- /*
- * Clear the offline cpus from @vector_cpumask for searching
- * and verify whether the result overlaps with @mask. If true,
- * then the call to apic->cpu_mask_to_apicid() will
- * succeed as well. If not, no point in trying to find a
- * vector in this mask.
- */
- cpumask_and(vector_searchmask, vector_cpumask, cpu_online_mask);
- if (!cpumask_intersects(vector_searchmask, mask))
- goto next_cpu;
-
- if (cpumask_subset(vector_cpumask, d->domain)) {
- if (cpumask_equal(vector_cpumask, d->domain))
- goto success;
- /*
- * Mark the cpus which are not longer in the mask for
- * cleanup.
- */
- cpumask_andnot(d->old_domain, d->domain, vector_cpumask);
- vector = d->cfg.vector;
- goto update;
- }
+ lockdep_assert_held(&vector_lock);
- vector = current_vector;
- offset = current_offset;
-next:
- vector += 16;
- if (vector >= FIRST_SYSTEM_VECTOR) {
- offset = (offset + 1) % 16;
- vector = FIRST_EXTERNAL_VECTOR + offset;
- }
+ trace_vector_update(irqd->irq, newvec, newcpu, apicd->cfg.vector,
+ apicd->cpu);
- /* If the search wrapped around, try the next cpu */
- if (unlikely(current_vector == vector))
- goto next_cpu;
+ /* Setup the vector move, if required */
+ if (apicd->cfg.vector && cpu_online(apicd->cpu)) {
+ apicd->move_in_progress = true;
+ apicd->cfg.old_vector = apicd->cfg.vector;
+ apicd->prev_cpu = apicd->cpu;
+ } else {
+ apicd->cfg.old_vector = 0;
+ }
- if (test_bit(vector, system_vectors))
- goto next;
+ apicd->cfg.vector = newvec;
+ apicd->cpu = newcpu;
+ BUG_ON(!IS_ERR_OR_NULL(per_cpu(vector_irq, newcpu)[newvec]));
+ per_cpu(vector_irq, newcpu)[newvec] = desc;
+}
- for_each_cpu(new_cpu, vector_searchmask) {
- if (!IS_ERR_OR_NULL(per_cpu(vector_irq, new_cpu)[vector]))
- goto next;
- }
- /* Found one! */
- current_vector = vector;
- current_offset = offset;
- /* Schedule the old vector for cleanup on all cpus */
- if (d->cfg.vector)
- cpumask_copy(d->old_domain, d->domain);
- for_each_cpu(new_cpu, vector_searchmask)
- per_cpu(vector_irq, new_cpu)[vector] = irq_to_desc(irq);
- goto update;
-
-next_cpu:
- /*
- * We exclude the current @vector_cpumask from the requested
- * @mask and try again with the next online cpu in the
- * result. We cannot modify @mask, so we use @vector_cpumask
- * as a temporary buffer here as it will be reassigned when
- * calling apic->vector_allocation_domain() above.
- */
- cpumask_or(searched_cpumask, searched_cpumask, vector_cpumask);
- cpumask_andnot(vector_cpumask, mask, searched_cpumask);
- cpu = cpumask_first_and(vector_cpumask, cpu_online_mask);
- continue;
- }
- return -ENOSPC;
+static int allocate_vector(struct irq_data *irqd, const struct cpumask *dest)
+{
+ struct apic_chip_data *apicd = apic_chip_data(irqd);
+ int vector = apicd->cfg.vector;
+ unsigned int cpu = apicd->cpu;
-update:
- /*
- * Exclude offline cpus from the cleanup mask and set the
- * move_in_progress flag when the result is not empty.
- */
- cpumask_and(d->old_domain, d->old_domain, cpu_online_mask);
- d->move_in_progress = !cpumask_empty(d->old_domain);
- d->cfg.old_vector = d->move_in_progress ? d->cfg.vector : 0;
- d->prev_cpu = d->cpu;
- d->cfg.vector = vector;
- cpumask_copy(d->domain, vector_cpumask);
-success:
/*
- * Cache destination APIC IDs into cfg->dest_apicid. This cannot fail
- * as we already established, that mask & d->domain & cpu_online_mask
- * is not empty.
- *
- * vector_searchmask is a subset of d->domain and has the offline
- * cpus masked out.
+ * If the current target CPU is online and in the new requested
+ * affinity mask, there is no point in moving the interrupt from
+ * one CPU to another.
*/
- cpumask_and(vector_searchmask, vector_searchmask, mask);
- BUG_ON(apic->cpu_mask_to_apicid(vector_searchmask, irqd,
- &d->cfg.dest_apicid));
- d->cpu = cpumask_first(vector_searchmask);
+ if (vector && cpu_online(cpu) && cpumask_test_cpu(cpu, dest))
+ return 0;
+
+ vector = irq_matrix_alloc(vector_matrix, dest, false, &cpu);
+ if (vector > 0)
+ apic_update_vector(irqd, vector, cpu);
+ trace_vector_alloc(irqd->irq, vector, false, vector);
+ return vector;
+}
+
+static int assign_vector_locked(struct irq_data *irqd,
+ const struct cpumask *dest)
+{
+ int vector = allocate_vector(irqd, dest);
+
+ if (vector < 0)
+ return vector;
+
+ apic_update_irq_cfg(irqd);
return 0;
}
-static int assign_irq_vector(int irq, struct apic_chip_data *apicd,
- const struct cpumask *mask,
- struct irq_data *irqd)
+static int assign_irq_vector(struct irq_data *irqd, const struct cpumask *dest)
{
- int err;
unsigned long flags;
+ int ret;
raw_spin_lock_irqsave(&vector_lock, flags);
- err = __assign_irq_vector(irq, apicd, mask, irqd);
+ cpumask_and(vector_searchmask, dest, cpu_online_mask);
+ ret = assign_vector_locked(irqd, vector_searchmask);
raw_spin_unlock_irqrestore(&vector_lock, flags);
- return err;
+ return ret;
}
-static int assign_irq_vector_policy(int irq, int node,
- struct apic_chip_data *apicd,
- struct irq_alloc_info *info,
- struct irq_data *irqd)
+static int assign_irq_vector_policy(struct irq_data *irqd,
+ struct irq_alloc_info *info, int node)
{
if (info->mask)
- return assign_irq_vector(irq, apicd, info->mask, irqd);
+ return assign_irq_vector(irqd, info->mask);
if (node != NUMA_NO_NODE &&
- assign_irq_vector(irq, apicd, cpumask_of_node(node), irqd) == 0)
+ !assign_irq_vector(irqd, cpumask_of_node(node)))
return 0;
- return assign_irq_vector(irq, apicd, cpu_online_mask, irqd);
+ return assign_irq_vector(irqd, cpu_online_mask);
}
-static void clear_irq_vector(int irq, struct apic_chip_data *apicd)
+static void clear_irq_vector(struct irq_data *irqd)
{
+ struct apic_chip_data *apicd = apic_chip_data(irqd);
unsigned int vector = apicd->cfg.vector;
+ lockdep_assert_held(&vector_lock);
if (!vector)
return;
+ trace_vector_clear(irqd->irq, vector, apicd->cpu, apicd->cfg.old_vector,
+ apicd->prev_cpu);
+
per_cpu(vector_irq, apicd->cpu)[vector] = VECTOR_UNUSED;
+ irq_matrix_free(vector_matrix, apicd->cpu, vector, false);
apicd->cfg.vector = 0;
/* Clean up move in progress */
@@ -297,6 +226,8 @@ static void clear_irq_vector(int irq, struct apic_chip_data *apicd)
return;
per_cpu(vector_irq, apicd->prev_cpu)[vector] = VECTOR_UNUSED;
+ irq_matrix_free(vector_matrix, apicd->prev_cpu, vector, false);
+ apicd->cfg.old_vector = 0;
apicd->move_in_progress = 0;
hlist_del_init(&apicd->clist);
}
@@ -313,7 +244,7 @@ static void x86_vector_free_irqs(struct irq_domain *domain,
irqd = irq_domain_get_irq_data(x86_vector_domain, virq + i);
if (irqd && irqd->chip_data) {
raw_spin_lock_irqsave(&vector_lock, flags);
- clear_irq_vector(virq + i, irqd->chip_data);
+ clear_irq_vector(irqd);
apicd = irqd->chip_data;
irq_domain_reset_irq_data(irqd);
raw_spin_unlock_irqrestore(&vector_lock, flags);
@@ -328,6 +259,7 @@ static int x86_vector_alloc_irqs(struct irq_domain *domain, unsigned int virq,
struct irq_alloc_info *info = arg;
struct apic_chip_data *apicd;
struct irq_data *irqd;
+ unsigned long flags;
int i, err, node;
if (disable_apic)
@@ -348,23 +280,30 @@ static int x86_vector_alloc_irqs(struct irq_domain *domain, unsigned int virq,
goto error;
}
+ apicd->irq = virq + i;
irqd->chip = &lapic_controller;
irqd->chip_data = apicd;
irqd->hwirq = virq + i;
irqd_set_single_target(irqd);
/*
- * Make sure, that the legacy to IOAPIC transition stays on
- * the same vector. This is required for check_timer() to
- * work correctly as it might switch back to legacy mode.
+ * Legacy vectors are already assigned when the IOAPIC
+ * takes them over. They stay on the same vector. This is
+ * required for check_timer() to work correctly as it might
+ * switch back to legacy mode. Only update the hardware
+ * config.
*/
if (info->flags & X86_IRQ_ALLOC_LEGACY) {
apicd->cfg.vector = ISA_IRQ_VECTOR(virq + i);
apicd->cpu = 0;
- cpumask_copy(apicd->domain, cpumask_of(0));
+ trace_vector_setup(virq + i, true, 0);
+ raw_spin_lock_irqsave(&vector_lock, flags);
+ apic_update_irq_cfg(irqd);
+ raw_spin_unlock_irqrestore(&vector_lock, flags);
+ continue;
}
- err = assign_irq_vector_policy(virq + i, node, apicd, info,
- irqd);
+ err = assign_irq_vector_policy(irqd, info, node);
+ trace_vector_setup(virq + i, false, err);
if (err)
goto error;
}
@@ -498,9 +437,7 @@ int __init arch_early_irq_init(void)
arch_init_msi_domain(x86_vector_domain);
arch_init_htirq_domain(x86_vector_domain);
- BUG_ON(!alloc_cpumask_var(&vector_cpumask, GFP_KERNEL));
BUG_ON(!alloc_cpumask_var(&vector_searchmask, GFP_KERNEL));
- BUG_ON(!alloc_cpumask_var(&searched_cpumask, GFP_KERNEL));
/*
* Allocate the vector matrix allocator data structure and limit the
@@ -523,8 +460,10 @@ static void vector_update_shutdown_irqs(void)
struct irq_data *irqd = irq_desc_get_irq_data(desc);
struct apic_chip_data *ad = apic_chip_data(irqd);
- if (ad && ad->cfg.vector && ad->cpu == smp_processor_id())
- this_cpu_write(vector_irq[ad->cfg.vector], desc);
+ if (!ad || !ad->cfg.vector || ad->cpu != smp_processor_id())
+ continue;
+ this_cpu_write(vector_irq[ad->cfg.vector], desc);
+ irq_matrix_assign(vector_matrix, ad->cfg.vector);
}
}
@@ -600,8 +539,7 @@ void apic_ack_edge(struct irq_data *irqd)
static int apic_set_affinity(struct irq_data *irqd,
const struct cpumask *dest, bool force)
{
- struct apic_chip_data *apicd = irqd->chip_data;
- int err, irq = irqd->irq;
+ int err;
if (!IS_ENABLED(CONFIG_SMP))
return -EPERM;
@@ -609,7 +547,7 @@ static int apic_set_affinity(struct irq_data *irqd,
if (!cpumask_intersects(dest, cpu_online_mask))
return -EINVAL;
- err = assign_irq_vector(irq, apicd, dest, irqd);
+ err = assign_irq_vector(irqd, dest);
return err ? err : IRQ_SET_MASK_OK;
}
@@ -622,6 +560,19 @@ static struct irq_chip lapic_controller = {
#ifdef CONFIG_SMP
+static void free_moved_vector(struct apic_chip_data *apicd)
+{
+ unsigned int vector = apicd->cfg.old_vector;
+ unsigned int cpu = apicd->prev_cpu;
+
+ trace_vector_free_moved(apicd->irq, vector, false);
+ irq_matrix_free(vector_matrix, cpu, vector, false);
+ __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
+ hlist_del_init(&apicd->clist);
+ apicd->cfg.old_vector = 0;
+ apicd->move_in_progress = 0;
+}
+
asmlinkage __visible void __irq_entry smp_irq_move_cleanup_interrupt(void)
{
struct hlist_head *clhead = this_cpu_ptr(&cleanup_list);
@@ -649,9 +600,7 @@ asmlinkage __visible void __irq_entry smp_irq_move_cleanup_interrupt(void)
apic->send_IPI_self(IRQ_MOVE_CLEANUP_VECTOR);
continue;
}
- hlist_del_init(&apicd->clist);
- __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
- apicd->cfg.old_vector = 0;
+ free_moved_vector(apicd);
}
raw_spin_unlock(&vector_lock);
@@ -786,12 +735,7 @@ void irq_force_complete_move(struct irq_desc *desc)
pr_warn("IRQ fixup: irq %d move in progress, old vector %d\n",
irqd->irq, vector);
}
- per_cpu(vector_irq, apicd->prev_cpu)[vector] = VECTOR_UNUSED;
- /* Cleanup the left overs of the (half finished) move */
- cpumask_clear(apicd->old_domain);
- apicd->cfg.old_vector = 0;
- apicd->move_in_progress = 0;
- hlist_del_init(&apicd->clist);
+ free_moved_vector(apicd);
unlock:
raw_spin_unlock(&vector_lock);
}