]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/perf/arm_pmu.c
drivers/perf: arm_pmu: manage interrupts per-cpu
[karo-tx-linux.git] / drivers / perf / arm_pmu.c
index 9612b84bc3e0084f072023f16fa97823d3602467..e984653b93aa8f77b0a2450f0118b4cc3bef2144 100644 (file)
@@ -617,94 +617,76 @@ static void cpu_pmu_disable_percpu_irq(void *data)
 
 static void cpu_pmu_free_irq(struct arm_pmu *cpu_pmu)
 {
-       int i, irq, irqs;
-       struct platform_device *pmu_device = cpu_pmu->plat_device;
+       int cpu;
        struct pmu_hw_events __percpu *hw_events = cpu_pmu->hw_events;
 
-       irqs = min(pmu_device->num_resources, num_possible_cpus());
-
-       irq = platform_get_irq(pmu_device, 0);
-       if (irq > 0 && irq_is_percpu(irq)) {
-               on_each_cpu_mask(&cpu_pmu->supported_cpus,
-                                cpu_pmu_disable_percpu_irq, &irq, 1);
-               free_percpu_irq(irq, &hw_events->percpu_pmu);
-       } else {
-               for (i = 0; i < irqs; ++i) {
-                       int cpu = i;
-
-                       if (cpu_pmu->irq_affinity)
-                               cpu = cpu_pmu->irq_affinity[i];
-
-                       if (!cpumask_test_and_clear_cpu(cpu, &cpu_pmu->active_irqs))
-                               continue;
-                       irq = platform_get_irq(pmu_device, i);
-                       if (irq > 0)
-                               free_irq(irq, per_cpu_ptr(&hw_events->percpu_pmu, cpu));
+       for_each_cpu(cpu, &cpu_pmu->supported_cpus) {
+               int irq = per_cpu(hw_events->irq, cpu);
+               if (!irq)
+                       continue;
+
+               if (irq_is_percpu(irq)) {
+                       on_each_cpu_mask(&cpu_pmu->supported_cpus,
+                                        cpu_pmu_disable_percpu_irq, &irq, 1);
+                       free_percpu_irq(irq, &hw_events->percpu_pmu);
+
+                       break;
                }
+
+               if (!cpumask_test_and_clear_cpu(cpu, &cpu_pmu->active_irqs))
+                       continue;
+
+               free_irq(irq, per_cpu_ptr(&hw_events->percpu_pmu, cpu));
        }
 }
 
 static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler)
 {
-       int i, err, irq, irqs;
-       struct platform_device *pmu_device = cpu_pmu->plat_device;
+       int cpu, err;
        struct pmu_hw_events __percpu *hw_events = cpu_pmu->hw_events;
 
-       if (!pmu_device)
-               return -ENODEV;
-
-       irqs = min(pmu_device->num_resources, num_possible_cpus());
-       if (irqs < 1) {
-               pr_warn_once("perf/ARM: No irqs for PMU defined, sampling events not supported\n");
-               return 0;
-       }
-
-       irq = platform_get_irq(pmu_device, 0);
-       if (irq > 0 && irq_is_percpu(irq)) {
-               err = request_percpu_irq(irq, handler, "arm-pmu",
-                                        &hw_events->percpu_pmu);
-               if (err) {
-                       pr_err("unable to request IRQ%d for ARM PMU counters\n",
-                               irq);
-                       return err;
-               }
-
-               on_each_cpu_mask(&cpu_pmu->supported_cpus,
-                                cpu_pmu_enable_percpu_irq, &irq, 1);
-       } else {
-               for (i = 0; i < irqs; ++i) {
-                       int cpu = i;
-
-                       err = 0;
-                       irq = platform_get_irq(pmu_device, i);
-                       if (irq < 0)
-                               continue;
-
-                       if (cpu_pmu->irq_affinity)
-                               cpu = cpu_pmu->irq_affinity[i];
-
-                       /*
-                        * If we have a single PMU interrupt that we can't shift,
-                        * assume that we're running on a uniprocessor machine and
-                        * continue. Otherwise, continue without this interrupt.
-                        */
-                       if (irq_set_affinity(irq, cpumask_of(cpu)) && irqs > 1) {
-                               pr_warn("unable to set irq affinity (irq=%d, cpu=%u)\n",
-                                       irq, cpu);
-                               continue;
-                       }
+       for_each_cpu(cpu, &cpu_pmu->supported_cpus) {
+               int irq = per_cpu(hw_events->irq, cpu);
+               if (!irq)
+                       continue;
 
-                       err = request_irq(irq, handler,
-                                         IRQF_NOBALANCING | IRQF_NO_THREAD, "arm-pmu",
-                                         per_cpu_ptr(&hw_events->percpu_pmu, cpu));
+               if (irq_is_percpu(irq)) {
+                       err = request_percpu_irq(irq, handler, "arm-pmu",
+                                                &hw_events->percpu_pmu);
                        if (err) {
                                pr_err("unable to request IRQ%d for ARM PMU counters\n",
                                        irq);
                                return err;
                        }
 
-                       cpumask_set_cpu(cpu, &cpu_pmu->active_irqs);
+                       on_each_cpu_mask(&cpu_pmu->supported_cpus,
+                                        cpu_pmu_enable_percpu_irq, &irq, 1);
+
+                       break;
+               }
+
+               /*
+                * If we have a single PMU interrupt that we can't shift,
+                * assume that we're running on a uniprocessor machine and
+                * continue. Otherwise, continue without this interrupt.
+                */
+               if (irq_set_affinity(irq, cpumask_of(cpu)) &&
+                   num_possible_cpus() > 1) {
+                       pr_warn("unable to set irq affinity (irq=%d, cpu=%u)\n",
+                               irq, cpu);
+                       continue;
                }
+
+               err = request_irq(irq, handler,
+                                 IRQF_NOBALANCING | IRQF_NO_THREAD, "arm-pmu",
+                                 per_cpu_ptr(&hw_events->percpu_pmu, cpu));
+               if (err) {
+                       pr_err("unable to request IRQ%d for ARM PMU counters\n",
+                               irq);
+                       return err;
+               }
+
+               cpumask_set_cpu(cpu, &cpu_pmu->active_irqs);
        }
 
        return 0;
@@ -828,29 +810,16 @@ static inline void cpu_pm_pmu_unregister(struct arm_pmu *cpu_pmu) { }
 static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
 {
        int err;
-       int cpu;
-       struct pmu_hw_events __percpu *cpu_hw_events;
-
-       cpu_hw_events = alloc_percpu(struct pmu_hw_events);
-       if (!cpu_hw_events)
-               return -ENOMEM;
 
        err = cpuhp_state_add_instance_nocalls(CPUHP_AP_PERF_ARM_STARTING,
                                               &cpu_pmu->node);
        if (err)
-               goto out_free;
+               goto out;
 
        err = cpu_pm_pmu_register(cpu_pmu);
        if (err)
                goto out_unregister;
 
-       for_each_possible_cpu(cpu) {
-               struct pmu_hw_events *events = per_cpu_ptr(cpu_hw_events, cpu);
-               raw_spin_lock_init(&events->pmu_lock);
-               events->percpu_pmu = cpu_pmu;
-       }
-
-       cpu_pmu->hw_events      = cpu_hw_events;
        cpu_pmu->request_irq    = cpu_pmu_request_irq;
        cpu_pmu->free_irq       = cpu_pmu_free_irq;
 
@@ -859,10 +828,6 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
                on_each_cpu_mask(&cpu_pmu->supported_cpus, cpu_pmu->reset,
                         cpu_pmu, 1);
 
-       /* If no interrupts available, set the corresponding capability flag */
-       if (!platform_get_irq(cpu_pmu->plat_device, 0))
-               cpu_pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
-
        /*
         * This is a CPU PMU potentially in a heterogeneous configuration (e.g.
         * big.LITTLE). This is not an uncore PMU, and we have taken ctx
@@ -876,8 +841,7 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
 out_unregister:
        cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_STARTING,
                                            &cpu_pmu->node);
-out_free:
-       free_percpu(cpu_hw_events);
+out:
        return err;
 }
 
@@ -886,7 +850,6 @@ static void cpu_pmu_destroy(struct arm_pmu *cpu_pmu)
        cpu_pm_pmu_unregister(cpu_pmu);
        cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_STARTING,
                                            &cpu_pmu->node);
-       free_percpu(cpu_pmu->hw_events);
 }
 
 /*
@@ -912,100 +875,174 @@ static int probe_current_pmu(struct arm_pmu *pmu,
        return ret;
 }
 
-static int of_pmu_irq_cfg(struct arm_pmu *pmu)
+static int pmu_parse_percpu_irq(struct arm_pmu *pmu, int irq)
 {
-       int *irqs, i = 0;
-       bool using_spi = false;
-       struct platform_device *pdev = pmu->plat_device;
+       int cpu, ret;
+       struct pmu_hw_events __percpu *hw_events = pmu->hw_events;
 
-       irqs = kcalloc(pdev->num_resources, sizeof(*irqs), GFP_KERNEL);
-       if (!irqs)
-               return -ENOMEM;
+       ret = irq_get_percpu_devid_partition(irq, &pmu->supported_cpus);
+       if (ret)
+               return ret;
 
-       do {
-               struct device_node *dn;
-               int cpu, irq;
+       for_each_cpu(cpu, &pmu->supported_cpus)
+               per_cpu(hw_events->irq, cpu) = irq;
 
-               /* See if we have an affinity entry */
-               dn = of_parse_phandle(pdev->dev.of_node, "interrupt-affinity", i);
-               if (!dn)
-                       break;
+       return 0;
+}
 
-               /* Check the IRQ type and prohibit a mix of PPIs and SPIs */
-               irq = platform_get_irq(pdev, i);
-               if (irq > 0) {
-                       bool spi = !irq_is_percpu(irq);
-
-                       if (i > 0 && spi != using_spi) {
-                               pr_err("PPI/SPI IRQ type mismatch for %s!\n",
-                                       dn->name);
-                               of_node_put(dn);
-                               kfree(irqs);
-                               return -EINVAL;
-                       }
+static bool pmu_has_irq_affinity(struct device_node *node)
+{
+       return !!of_find_property(node, "interrupt-affinity", NULL);
+}
 
-                       using_spi = spi;
-               }
+static int pmu_parse_irq_affinity(struct device_node *node, int i)
+{
+       struct device_node *dn;
+       int cpu;
 
-               /* Now look up the logical CPU number */
-               for_each_possible_cpu(cpu) {
-                       struct device_node *cpu_dn;
+       /*
+        * If we don't have an interrupt-affinity property, we guess irq
+        * affinity matches our logical CPU order, as we used to assume.
+        * This is fragile, so we'll warn in pmu_parse_irqs().
+        */
+       if (!pmu_has_irq_affinity(node))
+               return i;
 
-                       cpu_dn = of_cpu_device_node_get(cpu);
-                       of_node_put(cpu_dn);
+       dn = of_parse_phandle(node, "interrupt-affinity", i);
+       if (!dn) {
+               pr_warn("failed to parse interrupt-affinity[%d] for %s\n",
+                       i, node->name);
+               return -EINVAL;
+       }
 
-                       if (dn == cpu_dn)
-                               break;
-               }
+       /* Now look up the logical CPU number */
+       for_each_possible_cpu(cpu) {
+               struct device_node *cpu_dn;
 
-               if (cpu >= nr_cpu_ids) {
-                       pr_warn("Failed to find logical CPU for %s\n",
-                               dn->name);
-                       of_node_put(dn);
-                       cpumask_setall(&pmu->supported_cpus);
+               cpu_dn = of_cpu_device_node_get(cpu);
+               of_node_put(cpu_dn);
+
+               if (dn == cpu_dn)
                        break;
+       }
+
+       if (cpu >= nr_cpu_ids) {
+               pr_warn("failed to find logical CPU for %s\n", dn->name);
+       }
+
+       of_node_put(dn);
+
+       return cpu;
+}
+
+static int pmu_parse_irqs(struct arm_pmu *pmu)
+{
+       int i = 0, irqs;
+       struct platform_device *pdev = pmu->plat_device;
+       struct pmu_hw_events __percpu *hw_events = pmu->hw_events;
+
+       irqs = platform_irq_count(pdev);
+       if (irqs < 0) {
+               pr_err("unable to count PMU IRQs\n");
+               return irqs;
+       }
+
+       /*
+        * In this case we have no idea which CPUs are covered by the PMU.
+        * To match our prior behaviour, we assume all CPUs in this case.
+        */
+       if (irqs == 0) {
+               pr_warn("no irqs for PMU, sampling events not supported\n");
+               pmu->pmu.capabilities |= PERF_PMU_CAP_NO_INTERRUPT;
+               cpumask_setall(&pmu->supported_cpus);
+               return 0;
+       }
+
+       if (irqs == 1) {
+               int irq = platform_get_irq(pdev, 0);
+               if (irq && irq_is_percpu(irq))
+                       return pmu_parse_percpu_irq(pmu, irq);
+       }
+
+       if (!pmu_has_irq_affinity(pdev->dev.of_node)) {
+               pr_warn("no interrupt-affinity property for %s, guessing.\n",
+                       of_node_full_name(pdev->dev.of_node));
+       }
+
+       /*
+        * Some platforms have all PMU IRQs OR'd into a single IRQ, with a
+        * special platdata function that attempts to demux them.
+        */
+       if (dev_get_platdata(&pdev->dev))
+               cpumask_setall(&pmu->supported_cpus);
+
+       for (i = 0; i < irqs; i++) {
+               int cpu, irq;
+
+               irq = platform_get_irq(pdev, i);
+               if (WARN_ON(irq <= 0))
+                       continue;
+
+               if (irq_is_percpu(irq)) {
+                       pr_warn("multiple PPIs or mismatched SPI/PPI detected\n");
+                       return -EINVAL;
                }
-               of_node_put(dn);
 
-               /* For SPIs, we need to track the affinity per IRQ */
-               if (using_spi) {
-                       if (i >= pdev->num_resources)
-                               break;
+               cpu = pmu_parse_irq_affinity(pdev->dev.of_node, i);
+               if (cpu < 0)
+                       return cpu;
+               if (cpu >= nr_cpu_ids)
+                       continue;
 
-                       irqs[i] = cpu;
+               if (per_cpu(hw_events->irq, cpu)) {
+                       pr_warn("multiple PMU IRQs for the same CPU detected\n");
+                       return -EINVAL;
                }
 
-               /* Keep track of the CPUs containing this PMU type */
+               per_cpu(hw_events->irq, cpu) = irq;
                cpumask_set_cpu(cpu, &pmu->supported_cpus);
-               i++;
-       } while (1);
+       }
 
-       /* If we didn't manage to parse anything, try the interrupt affinity */
-       if (cpumask_weight(&pmu->supported_cpus) == 0) {
-               int irq = platform_get_irq(pdev, 0);
+       return 0;
+}
 
-               if (irq > 0 && irq_is_percpu(irq)) {
-                       /* If using PPIs, check the affinity of the partition */
-                       int ret;
+static struct arm_pmu *armpmu_alloc(void)
+{
+       struct arm_pmu *pmu;
+       int cpu;
 
-                       ret = irq_get_percpu_devid_partition(irq, &pmu->supported_cpus);
-                       if (ret) {
-                               kfree(irqs);
-                               return ret;
-                       }
-               } else {
-                       /* Otherwise default to all CPUs */
-                       cpumask_setall(&pmu->supported_cpus);
-               }
+       pmu = kzalloc(sizeof(*pmu), GFP_KERNEL);
+       if (!pmu) {
+               pr_info("failed to allocate PMU device!\n");
+               goto out;
        }
 
-       /* If we matched up the IRQ affinities, use them to route the SPIs */
-       if (using_spi && i == pdev->num_resources)
-               pmu->irq_affinity = irqs;
-       else
-               kfree(irqs);
+       pmu->hw_events = alloc_percpu(struct pmu_hw_events);
+       if (!pmu->hw_events) {
+               pr_info("failed to allocate per-cpu PMU data.\n");
+               goto out_free_pmu;
+       }
 
-       return 0;
+       for_each_possible_cpu(cpu) {
+               struct pmu_hw_events *events;
+
+               events = per_cpu_ptr(pmu->hw_events, cpu);
+               raw_spin_lock_init(&events->pmu_lock);
+               events->percpu_pmu = pmu;
+       }
+
+       return pmu;
+
+out_free_pmu:
+       kfree(pmu);
+out:
+       return NULL;
+}
+
+static void armpmu_free(struct arm_pmu *pmu)
+{
+       free_percpu(pmu->hw_events);
+       kfree(pmu);
 }
 
 int arm_pmu_device_probe(struct platform_device *pdev,
@@ -1018,16 +1055,18 @@ int arm_pmu_device_probe(struct platform_device *pdev,
        struct arm_pmu *pmu;
        int ret = -ENODEV;
 
-       pmu = kzalloc(sizeof(struct arm_pmu), GFP_KERNEL);
-       if (!pmu) {
-               pr_info("failed to allocate PMU device!\n");
+       pmu = armpmu_alloc();
+       if (!pmu)
                return -ENOMEM;
-       }
 
        armpmu_init(pmu);
 
        pmu->plat_device = pdev;
 
+       ret = pmu_parse_irqs(pmu);
+       if (ret)
+               goto out_free;
+
        if (node && (of_id = of_match_node(of_table, pdev->dev.of_node))) {
                init_fn = of_id->data;
 
@@ -1040,9 +1079,7 @@ int arm_pmu_device_probe(struct platform_device *pdev,
                        pmu->secure_access = false;
                }
 
-               ret = of_pmu_irq_cfg(pmu);
-               if (!ret)
-                       ret = init_fn(pmu);
+               ret = init_fn(pmu);
        } else if (probe_table) {
                cpumask_setall(&pmu->supported_cpus);
                ret = probe_current_pmu(pmu, probe_table);
@@ -1075,8 +1112,7 @@ out_destroy:
 out_free:
        pr_info("%s: failed to register PMU devices!\n",
                of_node_full_name(node));
-       kfree(pmu->irq_affinity);
-       kfree(pmu);
+       armpmu_free(pmu);
        return ret;
 }