Merge tag 'arm64-upstream' of git://git.kernel.org/pub/scm/linux/kernel/git/arm64...
[linux-2.6-microblaze.git] / drivers / perf / arm_cspmu / arm_cspmu.c
index e2b7827..42b7204 100644 (file)
@@ -16,7 +16,7 @@
  * The user should refer to the vendor technical documentation to get details
  * about the supported events.
  *
- * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
  *
  */
 
 #include <linux/interrupt.h>
 #include <linux/io-64-nonatomic-lo-hi.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/perf_event.h>
 #include <linux/platform_device.h>
 
 #include "arm_cspmu.h"
-#include "nvidia_cspmu.h"
 
 #define PMUNAME "arm_cspmu"
 #define DRVNAME "arm-cs-arch-pmu"
  */
 #define HILOHI_MAX_POLL        1000
 
-/* JEDEC-assigned JEP106 identification code */
-#define ARM_CSPMU_IMPL_ID_NVIDIA               0x36B
-
 static unsigned long arm_cspmu_cpuhp_state;
 
+static DEFINE_MUTEX(arm_cspmu_lock);
+
+static void arm_cspmu_set_ev_filter(struct arm_cspmu *cspmu,
+                                   struct hw_perf_event *hwc, u32 filter);
+
 static struct acpi_apmt_node *arm_cspmu_apmt_node(struct device *dev)
 {
        return *(struct acpi_apmt_node **)dev_get_platdata(dev);
@@ -373,27 +375,45 @@ static struct attribute_group arm_cspmu_cpumask_attr_group = {
        .attrs = arm_cspmu_cpumask_attrs,
 };
 
-struct impl_match {
-       u32 pmiidr;
-       u32 mask;
-       int (*impl_init_ops)(struct arm_cspmu *cspmu);
-};
-
-static const struct impl_match impl_match[] = {
+static struct arm_cspmu_impl_match impl_match[] = {
        {
-         .pmiidr = ARM_CSPMU_IMPL_ID_NVIDIA,
-         .mask = ARM_CSPMU_PMIIDR_IMPLEMENTER,
-         .impl_init_ops = nv_cspmu_init_ops
+               .module_name    = "nvidia_cspmu",
+               .pmiidr_val     = ARM_CSPMU_IMPL_ID_NVIDIA,
+               .pmiidr_mask    = ARM_CSPMU_PMIIDR_IMPLEMENTER,
+               .module         = NULL,
+               .impl_init_ops  = NULL,
        },
-       {}
+       {
+               .module_name    = "ampere_cspmu",
+               .pmiidr_val     = ARM_CSPMU_IMPL_ID_AMPERE,
+               .pmiidr_mask    = ARM_CSPMU_PMIIDR_IMPLEMENTER,
+               .module         = NULL,
+               .impl_init_ops  = NULL,
+       },
+
+       {0}
 };
 
+static struct arm_cspmu_impl_match *arm_cspmu_impl_match_get(u32 pmiidr)
+{
+       struct arm_cspmu_impl_match *match = impl_match;
+
+       for (; match->pmiidr_val; match++) {
+               u32 mask = match->pmiidr_mask;
+
+               if ((match->pmiidr_val & mask) == (pmiidr & mask))
+                       return match;
+       }
+
+       return NULL;
+}
+
 static int arm_cspmu_init_impl_ops(struct arm_cspmu *cspmu)
 {
-       int ret;
+       int ret = 0;
        struct arm_cspmu_impl_ops *impl_ops = &cspmu->impl.ops;
        struct acpi_apmt_node *apmt_node = arm_cspmu_apmt_node(cspmu->dev);
-       const struct impl_match *match = impl_match;
+       struct arm_cspmu_impl_match *match;
 
        /*
         * Get PMU implementer and product id from APMT node.
@@ -405,17 +425,36 @@ static int arm_cspmu_init_impl_ops(struct arm_cspmu *cspmu)
                                       readl(cspmu->base0 + PMIIDR);
 
        /* Find implementer specific attribute ops. */
-       for (; match->pmiidr; match++) {
-               const u32 mask = match->mask;
+       match = arm_cspmu_impl_match_get(cspmu->impl.pmiidr);
+
+       /* Load implementer module and initialize the callbacks. */
+       if (match) {
+               mutex_lock(&arm_cspmu_lock);
+
+               if (match->impl_init_ops) {
+                       /* Prevent unload until PMU registration is done. */
+                       if (try_module_get(match->module)) {
+                               cspmu->impl.module = match->module;
+                               cspmu->impl.match = match;
+                               ret = match->impl_init_ops(cspmu);
+                               if (ret)
+                                       module_put(match->module);
+                       } else {
+                               WARN(1, "arm_cspmu failed to get module: %s\n",
+                                       match->module_name);
+                               ret = -EINVAL;
+                       }
+               } else {
+                       request_module_nowait(match->module_name);
+                       ret = -EPROBE_DEFER;
+               }
 
-               if ((match->pmiidr & mask) == (cspmu->impl.pmiidr & mask)) {
-                       ret = match->impl_init_ops(cspmu);
-                       if (ret)
-                               return ret;
+               mutex_unlock(&arm_cspmu_lock);
 
-                       break;
-               }
-       }
+               if (ret)
+                       return ret;
+       } else
+               cspmu->impl.module = THIS_MODULE;
 
        /* Use default callbacks if implementer doesn't provide one. */
        CHECK_DEFAULT_IMPL_OPS(impl_ops, get_event_attrs);
@@ -426,6 +465,7 @@ static int arm_cspmu_init_impl_ops(struct arm_cspmu *cspmu)
        CHECK_DEFAULT_IMPL_OPS(impl_ops, event_type);
        CHECK_DEFAULT_IMPL_OPS(impl_ops, event_filter);
        CHECK_DEFAULT_IMPL_OPS(impl_ops, event_attr_is_visible);
+       CHECK_DEFAULT_IMPL_OPS(impl_ops, set_ev_filter);
 
        return 0;
 }
@@ -478,11 +518,6 @@ arm_cspmu_alloc_attr_group(struct arm_cspmu *cspmu)
        struct attribute_group **attr_groups = NULL;
        struct device *dev = cspmu->dev;
        const struct arm_cspmu_impl_ops *impl_ops = &cspmu->impl.ops;
-       int ret;
-
-       ret = arm_cspmu_init_impl_ops(cspmu);
-       if (ret)
-               return NULL;
 
        cspmu->identifier = impl_ops->get_identifier(cspmu);
        cspmu->name = impl_ops->get_name(cspmu);
@@ -549,7 +584,7 @@ static void arm_cspmu_disable(struct pmu *pmu)
 static int arm_cspmu_get_event_idx(struct arm_cspmu_hw_events *hw_events,
                                struct perf_event *event)
 {
-       int idx;
+       int idx, ret;
        struct arm_cspmu *cspmu = to_arm_cspmu(event->pmu);
 
        if (supports_cycle_counter(cspmu)) {
@@ -583,6 +618,12 @@ static int arm_cspmu_get_event_idx(struct arm_cspmu_hw_events *hw_events,
        if (idx >= cspmu->num_logical_ctrs)
                return -EAGAIN;
 
+       if (cspmu->impl.ops.validate_event) {
+               ret = cspmu->impl.ops.validate_event(cspmu, event);
+               if (ret)
+                       return ret;
+       }
+
        set_bit(idx, hw_events->used_ctrs);
 
        return idx;
@@ -696,7 +737,10 @@ static void arm_cspmu_write_counter(struct perf_event *event, u64 val)
        if (use_64b_counter_reg(cspmu)) {
                offset = counter_offset(sizeof(u64), event->hw.idx);
 
-               writeq(val, cspmu->base1 + offset);
+               if (cspmu->has_atomic_dword)
+                       writeq(val, cspmu->base1 + offset);
+               else
+                       lo_hi_writeq(val, cspmu->base1 + offset);
        } else {
                offset = counter_offset(sizeof(u32), event->hw.idx);
 
@@ -789,9 +833,9 @@ static inline void arm_cspmu_set_event(struct arm_cspmu *cspmu,
        writel(hwc->config, cspmu->base0 + offset);
 }
 
-static inline void arm_cspmu_set_ev_filter(struct arm_cspmu *cspmu,
-                                          struct hw_perf_event *hwc,
-                                          u32 filter)
+static void arm_cspmu_set_ev_filter(struct arm_cspmu *cspmu,
+                                       struct hw_perf_event *hwc,
+                                       u32 filter)
 {
        u32 offset = PMEVFILTR + (4 * hwc->idx);
 
@@ -823,7 +867,7 @@ static void arm_cspmu_start(struct perf_event *event, int pmu_flags)
                arm_cspmu_set_cc_filter(cspmu, filter);
        } else {
                arm_cspmu_set_event(cspmu, hwc);
-               arm_cspmu_set_ev_filter(cspmu, hwc, filter);
+               cspmu->impl.ops.set_ev_filter(cspmu, hwc, filter);
        }
 
        hwc->state = 0;
@@ -1061,7 +1105,7 @@ static int arm_cspmu_request_irq(struct arm_cspmu *cspmu)
 
 static inline int arm_cspmu_find_cpu_container(int cpu, u32 container_uid)
 {
-       u32 acpi_uid;
+       u64 acpi_uid;
        struct device *cpu_dev;
        struct acpi_device *acpi_dev;
 
@@ -1071,10 +1115,8 @@ static inline int arm_cspmu_find_cpu_container(int cpu, u32 container_uid)
 
        acpi_dev = ACPI_COMPANION(cpu_dev);
        while (acpi_dev) {
-               if (!strcmp(acpi_device_hid(acpi_dev),
-                           ACPI_PROCESSOR_CONTAINER_HID) &&
-                   !kstrtouint(acpi_device_uid(acpi_dev), 0, &acpi_uid) &&
-                   acpi_uid == container_uid)
+               if (acpi_dev_hid_uid_match(acpi_dev, ACPI_PROCESSOR_CONTAINER_HID, NULL) &&
+                   !acpi_dev_uid_to_integer(acpi_dev, &acpi_uid) && acpi_uid == container_uid)
                        return 0;
 
                acpi_dev = acpi_dev_parent(acpi_dev);
@@ -1149,7 +1191,7 @@ static int arm_cspmu_register_pmu(struct arm_cspmu *cspmu)
 
        cspmu->pmu = (struct pmu){
                .task_ctx_nr    = perf_invalid_context,
-               .module         = THIS_MODULE,
+               .module         = cspmu->impl.module,
                .pmu_enable     = arm_cspmu_enable,
                .pmu_disable    = arm_cspmu_disable,
                .event_init     = arm_cspmu_event_init,
@@ -1196,11 +1238,17 @@ static int arm_cspmu_device_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
-       ret = arm_cspmu_register_pmu(cspmu);
+       ret = arm_cspmu_init_impl_ops(cspmu);
        if (ret)
                return ret;
 
-       return 0;
+       ret = arm_cspmu_register_pmu(cspmu);
+
+       /* Matches arm_cspmu_init_impl_ops() above. */
+       if (cspmu->impl.module != THIS_MODULE)
+               module_put(cspmu->impl.module);
+
+       return ret;
 }
 
 static int arm_cspmu_device_remove(struct platform_device *pdev)
@@ -1300,6 +1348,75 @@ static void __exit arm_cspmu_exit(void)
        cpuhp_remove_multi_state(arm_cspmu_cpuhp_state);
 }
 
+int arm_cspmu_impl_register(const struct arm_cspmu_impl_match *impl_match)
+{
+       struct arm_cspmu_impl_match *match;
+       int ret = 0;
+
+       match = arm_cspmu_impl_match_get(impl_match->pmiidr_val);
+
+       if (match) {
+               mutex_lock(&arm_cspmu_lock);
+
+               if (!match->impl_init_ops) {
+                       match->module = impl_match->module;
+                       match->impl_init_ops = impl_match->impl_init_ops;
+               } else {
+                       /* Broken match table may contain non-unique entries */
+                       WARN(1, "arm_cspmu backend already registered for module: %s, pmiidr: 0x%x, mask: 0x%x\n",
+                               match->module_name,
+                               match->pmiidr_val,
+                               match->pmiidr_mask);
+
+                       ret = -EINVAL;
+               }
+
+               mutex_unlock(&arm_cspmu_lock);
+
+               if (!ret)
+                       ret = driver_attach(&arm_cspmu_driver.driver);
+       } else {
+               pr_err("arm_cspmu reg failed, unable to find a match for pmiidr: 0x%x\n",
+                       impl_match->pmiidr_val);
+
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(arm_cspmu_impl_register);
+
+static int arm_cspmu_match_device(struct device *dev, const void *match)
+{
+       struct arm_cspmu *cspmu = platform_get_drvdata(to_platform_device(dev));
+
+       return (cspmu && cspmu->impl.match == match) ? 1 : 0;
+}
+
+void arm_cspmu_impl_unregister(const struct arm_cspmu_impl_match *impl_match)
+{
+       struct device *dev;
+       struct arm_cspmu_impl_match *match;
+
+       match = arm_cspmu_impl_match_get(impl_match->pmiidr_val);
+
+       if (WARN_ON(!match))
+               return;
+
+       /* Unbind the driver from all matching backend devices. */
+       while ((dev = driver_find_device(&arm_cspmu_driver.driver, NULL,
+                       match, arm_cspmu_match_device)))
+               device_release_driver(dev);
+
+       mutex_lock(&arm_cspmu_lock);
+
+       match->module = NULL;
+       match->impl_init_ops = NULL;
+
+       mutex_unlock(&arm_cspmu_lock);
+}
+EXPORT_SYMBOL_GPL(arm_cspmu_impl_unregister);
+
 module_init(arm_cspmu_init);
 module_exit(arm_cspmu_exit);