Merge tag 'for-linus-5.11-rc4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git...
[platform/kernel/linux-starfive.git] / drivers / xen / events / events_base.c
index bbebe24..e850f79 100644 (file)
@@ -95,7 +95,8 @@ struct irq_info {
        struct list_head list;
        struct list_head eoi_list;
        short refcnt;
-       short spurious_cnt;
+       u8 spurious_cnt;
+       u8 is_accounted;
        enum xen_irq_type type; /* type */
        unsigned irq;
        evtchn_port_t evtchn;   /* event channel */
@@ -161,6 +162,9 @@ static DEFINE_PER_CPU(int [NR_VIRQS], virq_to_irq) = {[0 ... NR_VIRQS-1] = -1};
 /* IRQ <-> IPI mapping */
 static DEFINE_PER_CPU(int [XEN_NR_IPIS], ipi_to_irq) = {[0 ... XEN_NR_IPIS-1] = -1};
 
+/* Event channel distribution data */
+static atomic_t channels_on_cpu[NR_CPUS];
+
 static int **evtchn_to_irq;
 #ifdef CONFIG_X86
 static unsigned long *pirq_eoi_map;
@@ -257,6 +261,32 @@ static void set_info_for_irq(unsigned int irq, struct irq_info *info)
                irq_set_chip_data(irq, info);
 }
 
+/* Per CPU channel accounting */
+static void channels_on_cpu_dec(struct irq_info *info)
+{
+       if (!info->is_accounted)
+               return;
+
+       info->is_accounted = 0;
+
+       if (WARN_ON_ONCE(info->cpu >= nr_cpu_ids))
+               return;
+
+       WARN_ON_ONCE(!atomic_add_unless(&channels_on_cpu[info->cpu], -1 , 0));
+}
+
+static void channels_on_cpu_inc(struct irq_info *info)
+{
+       if (WARN_ON_ONCE(info->cpu >= nr_cpu_ids))
+               return;
+
+       if (WARN_ON_ONCE(!atomic_add_unless(&channels_on_cpu[info->cpu], 1,
+                                           INT_MAX)))
+               return;
+
+       info->is_accounted = 1;
+}
+
 /* Constructors for packed IRQ information. */
 static int xen_irq_info_common_setup(struct irq_info *info,
                                     unsigned irq,
@@ -339,6 +369,7 @@ static void xen_irq_info_cleanup(struct irq_info *info)
 {
        set_evtchn_to_irq(info->evtchn, -1);
        info->evtchn = 0;
+       channels_on_cpu_dec(info);
 }
 
 /*
@@ -433,18 +464,25 @@ static bool pirq_needs_eoi_flag(unsigned irq)
        return info->u.pirq.flags & PIRQ_NEEDS_EOI;
 }
 
-static void bind_evtchn_to_cpu(evtchn_port_t evtchn, unsigned int cpu)
+static void bind_evtchn_to_cpu(evtchn_port_t evtchn, unsigned int cpu,
+                              bool force_affinity)
 {
        int irq = get_evtchn_to_irq(evtchn);
        struct irq_info *info = info_for_irq(irq);
 
        BUG_ON(irq == -1);
-#ifdef CONFIG_SMP
-       cpumask_copy(irq_get_affinity_mask(irq), cpumask_of(cpu));
-#endif
+
+       if (IS_ENABLED(CONFIG_SMP) && force_affinity) {
+               cpumask_copy(irq_get_affinity_mask(irq), cpumask_of(cpu));
+               cpumask_copy(irq_get_effective_affinity_mask(irq),
+                            cpumask_of(cpu));
+       }
+
        xen_evtchn_port_bind_to_cpu(evtchn, cpu, info->cpu);
 
+       channels_on_cpu_dec(info);
        info->cpu = cpu;
+       channels_on_cpu_inc(info);
 }
 
 /**
@@ -523,8 +561,10 @@ static void xen_irq_lateeoi_locked(struct irq_info *info, bool spurious)
                return;
 
        if (spurious) {
-               if ((1 << info->spurious_cnt) < (HZ << 2))
-                       info->spurious_cnt++;
+               if ((1 << info->spurious_cnt) < (HZ << 2)) {
+                       if (info->spurious_cnt != 0xFF)
+                               info->spurious_cnt++;
+               }
                if (info->spurious_cnt > 1) {
                        delay = 1 << (info->spurious_cnt - 2);
                        if (delay > HZ)
@@ -615,11 +655,6 @@ static void xen_irq_init(unsigned irq)
 {
        struct irq_info *info;
 
-#ifdef CONFIG_SMP
-       /* By default all event channels notify CPU#0. */
-       cpumask_copy(irq_get_affinity_mask(irq), cpumask_of(0));
-#endif
-
        info = kzalloc(sizeof(*info), GFP_KERNEL);
        if (info == NULL)
                panic("Unable to allocate metadata for IRQ%d\n", irq);
@@ -628,6 +663,11 @@ static void xen_irq_init(unsigned irq)
        info->refcnt = -1;
 
        set_info_for_irq(irq, info);
+       /*
+        * Interrupt affinity setting can be immediate. No point
+        * in delaying it until an interrupt is handled.
+        */
+       irq_set_status_flags(irq, IRQ_MOVE_PCNTXT);
 
        INIT_LIST_HEAD(&info->eoi_list);
        list_add_tail(&info->list, &xen_irq_list_head);
@@ -739,18 +779,7 @@ static void eoi_pirq(struct irq_data *data)
        if (!VALID_EVTCHN(evtchn))
                return;
 
-       if (unlikely(irqd_is_setaffinity_pending(data)) &&
-           likely(!irqd_irq_disabled(data))) {
-               int masked = test_and_set_mask(evtchn);
-
-               clear_evtchn(evtchn);
-
-               irq_move_masked_irq(data);
-
-               if (!masked)
-                       unmask_evtchn(evtchn);
-       } else
-               clear_evtchn(evtchn);
+       clear_evtchn(evtchn);
 
        if (pirq_needs_eoi(data->irq)) {
                rc = HYPERVISOR_physdev_op(PHYSDEVOP_eoi, &eoi);
@@ -794,7 +823,7 @@ static unsigned int __startup_pirq(unsigned int irq)
                goto err;
 
        info->evtchn = evtchn;
-       bind_evtchn_to_cpu(evtchn, 0);
+       bind_evtchn_to_cpu(evtchn, 0, false);
 
        rc = xen_evtchn_port_setup(evtchn);
        if (rc)
@@ -1113,8 +1142,14 @@ static int bind_evtchn_to_irq_chip(evtchn_port_t evtchn, struct irq_chip *chip)
                        irq = ret;
                        goto out;
                }
-               /* New interdomain events are bound to VCPU 0. */
-               bind_evtchn_to_cpu(evtchn, 0);
+               /*
+                * New interdomain events are initially bound to vCPU0 This
+                * is required to setup the event channel in the first
+                * place and also important for UP guests because the
+                * affinity setting is not invoked on them so nothing would
+                * bind the channel.
+                */
+               bind_evtchn_to_cpu(evtchn, 0, false);
        } else {
                struct irq_info *info = info_for_irq(irq);
                WARN_ON(info == NULL || info->type != IRQT_EVTCHN);
@@ -1132,12 +1167,6 @@ int bind_evtchn_to_irq(evtchn_port_t evtchn)
 }
 EXPORT_SYMBOL_GPL(bind_evtchn_to_irq);
 
-int bind_evtchn_to_irq_lateeoi(evtchn_port_t evtchn)
-{
-       return bind_evtchn_to_irq_chip(evtchn, &xen_lateeoi_chip);
-}
-EXPORT_SYMBOL_GPL(bind_evtchn_to_irq_lateeoi);
-
 static int bind_ipi_to_irq(unsigned int ipi, unsigned int cpu)
 {
        struct evtchn_bind_ipi bind_ipi;
@@ -1168,7 +1197,11 @@ static int bind_ipi_to_irq(unsigned int ipi, unsigned int cpu)
                        irq = ret;
                        goto out;
                }
-               bind_evtchn_to_cpu(evtchn, cpu);
+               /*
+                * Force the affinity mask to the target CPU so proc shows
+                * the correct target.
+                */
+               bind_evtchn_to_cpu(evtchn, cpu, true);
        } else {
                struct irq_info *info = info_for_irq(irq);
                WARN_ON(info == NULL || info->type != IRQT_IPI);
@@ -1281,7 +1314,11 @@ int bind_virq_to_irq(unsigned int virq, unsigned int cpu, bool percpu)
                        goto out;
                }
 
-               bind_evtchn_to_cpu(evtchn, cpu);
+               /*
+                * Force the affinity mask for percpu interrupts so proc
+                * shows the correct target.
+                */
+               bind_evtchn_to_cpu(evtchn, cpu, percpu);
        } else {
                struct irq_info *info = info_for_irq(irq);
                WARN_ON(info == NULL || info->type != IRQT_VIRQ);
@@ -1646,9 +1683,7 @@ void rebind_evtchn_irq(evtchn_port_t evtchn, int irq)
 
        mutex_unlock(&irq_mapping_update_lock);
 
-        bind_evtchn_to_cpu(evtchn, info->cpu);
-       /* This will be deferred until interrupt is processed */
-       irq_set_affinity(irq, cpumask_of(info->cpu));
+       bind_evtchn_to_cpu(evtchn, info->cpu, false);
 
        /* Unmask the event channel. */
        enable_irq(irq);
@@ -1682,7 +1717,7 @@ static int xen_rebind_evtchn_to_cpu(evtchn_port_t evtchn, unsigned int tcpu)
         * it, but don't do the xenlinux-level rebind in that case.
         */
        if (HYPERVISOR_event_channel_op(EVTCHNOP_bind_vcpu, &bind_vcpu) >= 0)
-               bind_evtchn_to_cpu(evtchn, tcpu);
+               bind_evtchn_to_cpu(evtchn, tcpu, false);
 
        if (!masked)
                unmask_evtchn(evtchn);
@@ -1690,27 +1725,47 @@ static int xen_rebind_evtchn_to_cpu(evtchn_port_t evtchn, unsigned int tcpu)
        return 0;
 }
 
+/*
+ * Find the CPU within @dest mask which has the least number of channels
+ * assigned. This is not precise as the per cpu counts can be modified
+ * concurrently.
+ */
+static unsigned int select_target_cpu(const struct cpumask *dest)
+{
+       unsigned int cpu, best_cpu = UINT_MAX, minch = UINT_MAX;
+
+       for_each_cpu_and(cpu, dest, cpu_online_mask) {
+               unsigned int curch = atomic_read(&channels_on_cpu[cpu]);
+
+               if (curch < minch) {
+                       minch = curch;
+                       best_cpu = cpu;
+               }
+       }
+
+       /*
+        * Catch the unlikely case that dest contains no online CPUs. Can't
+        * recurse.
+        */
+       if (best_cpu == UINT_MAX)
+               return select_target_cpu(cpu_online_mask);
+
+       return best_cpu;
+}
+
 static int set_affinity_irq(struct irq_data *data, const struct cpumask *dest,
                            bool force)
 {
-       unsigned tcpu = cpumask_first_and(dest, cpu_online_mask);
-       int ret = xen_rebind_evtchn_to_cpu(evtchn_from_irq(data->irq), tcpu);
+       unsigned int tcpu = select_target_cpu(dest);
+       int ret;
 
+       ret = xen_rebind_evtchn_to_cpu(evtchn_from_irq(data->irq), tcpu);
        if (!ret)
                irq_data_update_effective_affinity(data, cpumask_of(tcpu));
 
        return ret;
 }
 
-/* To be called with desc->lock held. */
-int xen_set_affinity_evtchn(struct irq_desc *desc, unsigned int tcpu)
-{
-       struct irq_data *d = irq_desc_get_irq_data(desc);
-
-       return set_affinity_irq(d, cpumask_of(tcpu), false);
-}
-EXPORT_SYMBOL_GPL(xen_set_affinity_evtchn);
-
 static void enable_dynirq(struct irq_data *data)
 {
        evtchn_port_t evtchn = evtchn_from_irq(data->irq);
@@ -1734,18 +1789,7 @@ static void ack_dynirq(struct irq_data *data)
        if (!VALID_EVTCHN(evtchn))
                return;
 
-       if (unlikely(irqd_is_setaffinity_pending(data)) &&
-           likely(!irqd_irq_disabled(data))) {
-               int masked = test_and_set_mask(evtchn);
-
-               clear_evtchn(evtchn);
-
-               irq_move_masked_irq(data);
-
-               if (!masked)
-                       unmask_evtchn(evtchn);
-       } else
-               clear_evtchn(evtchn);
+       clear_evtchn(evtchn);
 }
 
 static void mask_ack_dynirq(struct irq_data *data)
@@ -1830,7 +1874,8 @@ static void restore_cpu_virqs(unsigned int cpu)
 
                /* Record the new mapping. */
                (void)xen_irq_info_virq_setup(cpu, irq, evtchn, virq);
-               bind_evtchn_to_cpu(evtchn, cpu);
+               /* The affinity mask is still valid */
+               bind_evtchn_to_cpu(evtchn, cpu, false);
        }
 }
 
@@ -1855,7 +1900,8 @@ static void restore_cpu_ipis(unsigned int cpu)
 
                /* Record the new mapping. */
                (void)xen_irq_info_ipi_setup(cpu, irq, evtchn, ipi);
-               bind_evtchn_to_cpu(evtchn, cpu);
+               /* The affinity mask is still valid */
+               bind_evtchn_to_cpu(evtchn, cpu, false);
        }
 }
 
@@ -1938,8 +1984,12 @@ void xen_irq_resume(void)
        xen_evtchn_resume();
 
        /* No IRQ <-> event-channel mappings. */
-       list_for_each_entry(info, &xen_irq_list_head, list)
-               info->evtchn = 0; /* zap event-channel binding */
+       list_for_each_entry(info, &xen_irq_list_head, list) {
+               /* Zap event-channel binding */
+               info->evtchn = 0;
+               /* Adjust accounting */
+               channels_on_cpu_dec(info);
+       }
 
        clear_evtchn_to_irq_all();