]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/misc/cxl/api.c
Merge tag 'sh-for-4.8' of git://git.libc.org/linux-sh
[karo-tx-linux.git] / drivers / misc / cxl / api.c
index 6d228ccd884d14d1eebb46ba5a02c3830ea787bd..f3d34b941f8599e91e121b7dfe8cbc115e9f138e 100644 (file)
@@ -13,6 +13,8 @@
 #include <linux/file.h>
 #include <misc/cxl.h>
 #include <linux/fs.h>
+#include <asm/pnv-pci.h>
+#include <linux/msi.h>
 
 #include "cxl.h"
 
@@ -24,6 +26,8 @@ struct cxl_context *cxl_dev_context_init(struct pci_dev *dev)
        int rc;
 
        afu = cxl_pci_to_afu(dev);
+       if (IS_ERR(afu))
+               return ERR_CAST(afu);
 
        ctx = cxl_context_alloc();
        if (IS_ERR(ctx)) {
@@ -94,6 +98,42 @@ static irq_hw_number_t cxl_find_afu_irq(struct cxl_context *ctx, int num)
        return 0;
 }
 
+int _cxl_next_msi_hwirq(struct pci_dev *pdev, struct cxl_context **ctx, int *afu_irq)
+{
+       if (*ctx == NULL || *afu_irq == 0) {
+               *afu_irq = 1;
+               *ctx = cxl_get_context(pdev);
+       } else {
+               (*afu_irq)++;
+               if (*afu_irq > cxl_get_max_irqs_per_process(pdev)) {
+                       *ctx = list_next_entry(*ctx, extra_irq_contexts);
+                       *afu_irq = 1;
+               }
+       }
+       return cxl_find_afu_irq(*ctx, *afu_irq);
+}
+/* Exported via cxl_base */
+
+int cxl_set_priv(struct cxl_context *ctx, void *priv)
+{
+       if (!ctx)
+               return -EINVAL;
+
+       ctx->priv = priv;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cxl_set_priv);
+
+void *cxl_get_priv(struct cxl_context *ctx)
+{
+       if (!ctx)
+               return ERR_PTR(-EINVAL);
+
+       return ctx->priv;
+}
+EXPORT_SYMBOL_GPL(cxl_get_priv);
+
 int cxl_allocate_afu_irqs(struct cxl_context *ctx, int num)
 {
        int res;
@@ -102,7 +142,10 @@ int cxl_allocate_afu_irqs(struct cxl_context *ctx, int num)
        if (num == 0)
                num = ctx->afu->pp_irqs;
        res = afu_allocate_irqs(ctx, num);
-       if (!res && !cpu_has_feature(CPU_FTR_HVMODE)) {
+       if (res)
+               return res;
+
+       if (!cpu_has_feature(CPU_FTR_HVMODE)) {
                /* In a guest, the PSL interrupt is not multiplexed. It was
                 * allocated above, and we need to set its handler
                 */
@@ -110,6 +153,13 @@ int cxl_allocate_afu_irqs(struct cxl_context *ctx, int num)
                if (hwirq)
                        cxl_map_irq(ctx->afu->adapter, hwirq, cxl_ops->psl_interrupt, ctx, "psl");
        }
+
+       if (ctx->status == STARTED) {
+               if (cxl_ops->update_ivtes)
+                       cxl_ops->update_ivtes(ctx);
+               else WARN(1, "BUG: cxl_allocate_afu_irqs must be called prior to starting the context on this platform\n");
+       }
+
        return res;
 }
 EXPORT_SYMBOL_GPL(cxl_allocate_afu_irqs);
@@ -323,6 +373,23 @@ struct cxl_context *cxl_fops_get_context(struct file *file)
 }
 EXPORT_SYMBOL_GPL(cxl_fops_get_context);
 
+void cxl_set_driver_ops(struct cxl_context *ctx,
+                       struct cxl_afu_driver_ops *ops)
+{
+       WARN_ON(!ops->fetch_event || !ops->event_delivered);
+       atomic_set(&ctx->afu_driver_events, 0);
+       ctx->afu_driver_ops = ops;
+}
+EXPORT_SYMBOL_GPL(cxl_set_driver_ops);
+
+void cxl_context_events_pending(struct cxl_context *ctx,
+                               unsigned int new_events)
+{
+       atomic_add(new_events, &ctx->afu_driver_events);
+       wake_up_all(&ctx->wq);
+}
+EXPORT_SYMBOL_GPL(cxl_context_events_pending);
+
 int cxl_start_work(struct cxl_context *ctx,
                   struct cxl_ioctl_start_work *work)
 {
@@ -390,7 +457,106 @@ EXPORT_SYMBOL_GPL(cxl_perst_reloads_same_image);
 ssize_t cxl_read_adapter_vpd(struct pci_dev *dev, void *buf, size_t count)
 {
        struct cxl_afu *afu = cxl_pci_to_afu(dev);
+       if (IS_ERR(afu))
+               return -ENODEV;
 
        return cxl_ops->read_adapter_vpd(afu->adapter, buf, count);
 }
 EXPORT_SYMBOL_GPL(cxl_read_adapter_vpd);
+
+int cxl_set_max_irqs_per_process(struct pci_dev *dev, int irqs)
+{
+       struct cxl_afu *afu = cxl_pci_to_afu(dev);
+       if (IS_ERR(afu))
+               return -ENODEV;
+
+       if (irqs > afu->adapter->user_irqs)
+               return -EINVAL;
+
+       /* Limit user_irqs to prevent the user increasing this via sysfs */
+       afu->adapter->user_irqs = irqs;
+       afu->irqs_max = irqs;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(cxl_set_max_irqs_per_process);
+
+int cxl_get_max_irqs_per_process(struct pci_dev *dev)
+{
+       struct cxl_afu *afu = cxl_pci_to_afu(dev);
+       if (IS_ERR(afu))
+               return -ENODEV;
+
+       return afu->irqs_max;
+}
+EXPORT_SYMBOL_GPL(cxl_get_max_irqs_per_process);
+
+/*
+ * This is a special interrupt allocation routine called from the PHB's MSI
+ * setup function. When capi interrupts are allocated in this manner they must
+ * still be associated with a running context, but since the MSI APIs have no
+ * way to specify this we use the default context associated with the device.
+ *
+ * The Mellanox CX4 has a hardware limitation that restricts the maximum AFU
+ * interrupt number, so in order to overcome this their driver informs us of
+ * the restriction by setting the maximum interrupts per context, and we
+ * allocate additional contexts as necessary so that we can keep the AFU
+ * interrupt number within the supported range.
+ */
+int _cxl_cx4_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
+{
+       struct cxl_context *ctx, *new_ctx, *default_ctx;
+       int remaining;
+       int rc;
+
+       ctx = default_ctx = cxl_get_context(pdev);
+       if (WARN_ON(!default_ctx))
+               return -ENODEV;
+
+       remaining = nvec;
+       while (remaining > 0) {
+               rc = cxl_allocate_afu_irqs(ctx, min(remaining, ctx->afu->irqs_max));
+               if (rc) {
+                       pr_warn("%s: Failed to find enough free MSIs\n", pci_name(pdev));
+                       return rc;
+               }
+               remaining -= ctx->afu->irqs_max;
+
+               if (ctx != default_ctx && default_ctx->status == STARTED) {
+                       WARN_ON(cxl_start_context(ctx,
+                               be64_to_cpu(default_ctx->elem->common.wed),
+                               NULL));
+               }
+
+               if (remaining > 0) {
+                       new_ctx = cxl_dev_context_init(pdev);
+                       if (!new_ctx) {
+                               pr_warn("%s: Failed to allocate enough contexts for MSIs\n", pci_name(pdev));
+                               return -ENOSPC;
+                       }
+                       list_add(&new_ctx->extra_irq_contexts, &ctx->extra_irq_contexts);
+                       ctx = new_ctx;
+               }
+       }
+
+       return 0;
+}
+/* Exported via cxl_base */
+
+void _cxl_cx4_teardown_msi_irqs(struct pci_dev *pdev)
+{
+       struct cxl_context *ctx, *pos, *tmp;
+
+       ctx = cxl_get_context(pdev);
+       if (WARN_ON(!ctx))
+               return;
+
+       cxl_free_afu_irqs(ctx);
+       list_for_each_entry_safe(pos, tmp, &ctx->extra_irq_contexts, extra_irq_contexts) {
+               cxl_stop_context(pos);
+               cxl_free_afu_irqs(pos);
+               list_del(&pos->extra_irq_contexts);
+               cxl_release_context(pos);
+       }
+}
+/* Exported via cxl_base */