Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / net / ethernet / mellanox / mlx5 / core / eq.c
diff --git a/kernel/drivers/net/ethernet/mellanox/mlx5/core/eq.c b/kernel/drivers/net/ethernet/mellanox/mlx5/core/eq.c
new file mode 100644 (file)
index 0000000..58800e4
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2013-2015, Mellanox Technologies. All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses.  You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ *     Redistribution and use in source and binary forms, with or
+ *     without modification, are permitted provided that the following
+ *     conditions are met:
+ *
+ *      - Redistributions of source code must retain the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer.
+ *
+ *      - Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials
+ *        provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mlx5/driver.h>
+#include <linux/mlx5/cmd.h>
+#include "mlx5_core.h"
+
+enum {
+       MLX5_EQE_SIZE           = sizeof(struct mlx5_eqe),
+       MLX5_EQE_OWNER_INIT_VAL = 0x1,
+};
+
+enum {
+       MLX5_EQ_STATE_ARMED             = 0x9,
+       MLX5_EQ_STATE_FIRED             = 0xa,
+       MLX5_EQ_STATE_ALWAYS_ARMED      = 0xb,
+};
+
+enum {
+       MLX5_NUM_SPARE_EQE      = 0x80,
+       MLX5_NUM_ASYNC_EQE      = 0x100,
+       MLX5_NUM_CMD_EQE        = 32,
+};
+
+enum {
+       MLX5_EQ_DOORBEL_OFFSET  = 0x40,
+};
+
+#define MLX5_ASYNC_EVENT_MASK ((1ull << MLX5_EVENT_TYPE_PATH_MIG)          | \
+                              (1ull << MLX5_EVENT_TYPE_COMM_EST)           | \
+                              (1ull << MLX5_EVENT_TYPE_SQ_DRAINED)         | \
+                              (1ull << MLX5_EVENT_TYPE_CQ_ERROR)           | \
+                              (1ull << MLX5_EVENT_TYPE_WQ_CATAS_ERROR)     | \
+                              (1ull << MLX5_EVENT_TYPE_PATH_MIG_FAILED)    | \
+                              (1ull << MLX5_EVENT_TYPE_WQ_INVAL_REQ_ERROR) | \
+                              (1ull << MLX5_EVENT_TYPE_WQ_ACCESS_ERROR)    | \
+                              (1ull << MLX5_EVENT_TYPE_PORT_CHANGE)        | \
+                              (1ull << MLX5_EVENT_TYPE_SRQ_CATAS_ERROR)    | \
+                              (1ull << MLX5_EVENT_TYPE_SRQ_LAST_WQE)       | \
+                              (1ull << MLX5_EVENT_TYPE_SRQ_RQ_LIMIT))
+
+struct map_eq_in {
+       u64     mask;
+       u32     reserved;
+       u32     unmap_eqn;
+};
+
+struct cre_des_eq {
+       u8      reserved[15];
+       u8      eqn;
+};
+
+static int mlx5_cmd_destroy_eq(struct mlx5_core_dev *dev, u8 eqn)
+{
+       struct mlx5_destroy_eq_mbox_in in;
+       struct mlx5_destroy_eq_mbox_out out;
+       int err;
+
+       memset(&in, 0, sizeof(in));
+       memset(&out, 0, sizeof(out));
+       in.hdr.opcode = cpu_to_be16(MLX5_CMD_OP_DESTROY_EQ);
+       in.eqn = eqn;
+       err = mlx5_cmd_exec(dev, &in, sizeof(in), &out, sizeof(out));
+       if (!err)
+               goto ex;
+
+       if (out.hdr.status)
+               err = mlx5_cmd_status_to_err(&out.hdr);
+
+ex:
+       return err;
+}
+
+static struct mlx5_eqe *get_eqe(struct mlx5_eq *eq, u32 entry)
+{
+       return mlx5_buf_offset(&eq->buf, entry * MLX5_EQE_SIZE);
+}
+
+static struct mlx5_eqe *next_eqe_sw(struct mlx5_eq *eq)
+{
+       struct mlx5_eqe *eqe = get_eqe(eq, eq->cons_index & (eq->nent - 1));
+
+       return ((eqe->owner & 1) ^ !!(eq->cons_index & eq->nent)) ? NULL : eqe;
+}
+
+static const char *eqe_type_str(u8 type)
+{
+       switch (type) {
+       case MLX5_EVENT_TYPE_COMP:
+               return "MLX5_EVENT_TYPE_COMP";
+       case MLX5_EVENT_TYPE_PATH_MIG:
+               return "MLX5_EVENT_TYPE_PATH_MIG";
+       case MLX5_EVENT_TYPE_COMM_EST:
+               return "MLX5_EVENT_TYPE_COMM_EST";
+       case MLX5_EVENT_TYPE_SQ_DRAINED:
+               return "MLX5_EVENT_TYPE_SQ_DRAINED";
+       case MLX5_EVENT_TYPE_SRQ_LAST_WQE:
+               return "MLX5_EVENT_TYPE_SRQ_LAST_WQE";
+       case MLX5_EVENT_TYPE_SRQ_RQ_LIMIT:
+               return "MLX5_EVENT_TYPE_SRQ_RQ_LIMIT";
+       case MLX5_EVENT_TYPE_CQ_ERROR:
+               return "MLX5_EVENT_TYPE_CQ_ERROR";
+       case MLX5_EVENT_TYPE_WQ_CATAS_ERROR:
+               return "MLX5_EVENT_TYPE_WQ_CATAS_ERROR";
+       case MLX5_EVENT_TYPE_PATH_MIG_FAILED:
+               return "MLX5_EVENT_TYPE_PATH_MIG_FAILED";
+       case MLX5_EVENT_TYPE_WQ_INVAL_REQ_ERROR:
+               return "MLX5_EVENT_TYPE_WQ_INVAL_REQ_ERROR";
+       case MLX5_EVENT_TYPE_WQ_ACCESS_ERROR:
+               return "MLX5_EVENT_TYPE_WQ_ACCESS_ERROR";
+       case MLX5_EVENT_TYPE_SRQ_CATAS_ERROR:
+               return "MLX5_EVENT_TYPE_SRQ_CATAS_ERROR";
+       case MLX5_EVENT_TYPE_INTERNAL_ERROR:
+               return "MLX5_EVENT_TYPE_INTERNAL_ERROR";
+       case MLX5_EVENT_TYPE_PORT_CHANGE:
+               return "MLX5_EVENT_TYPE_PORT_CHANGE";
+       case MLX5_EVENT_TYPE_GPIO_EVENT:
+               return "MLX5_EVENT_TYPE_GPIO_EVENT";
+       case MLX5_EVENT_TYPE_REMOTE_CONFIG:
+               return "MLX5_EVENT_TYPE_REMOTE_CONFIG";
+       case MLX5_EVENT_TYPE_DB_BF_CONGESTION:
+               return "MLX5_EVENT_TYPE_DB_BF_CONGESTION";
+       case MLX5_EVENT_TYPE_STALL_EVENT:
+               return "MLX5_EVENT_TYPE_STALL_EVENT";
+       case MLX5_EVENT_TYPE_CMD:
+               return "MLX5_EVENT_TYPE_CMD";
+       case MLX5_EVENT_TYPE_PAGE_REQUEST:
+               return "MLX5_EVENT_TYPE_PAGE_REQUEST";
+       case MLX5_EVENT_TYPE_PAGE_FAULT:
+               return "MLX5_EVENT_TYPE_PAGE_FAULT";
+       default:
+               return "Unrecognized event";
+       }
+}
+
+static enum mlx5_dev_event port_subtype_event(u8 subtype)
+{
+       switch (subtype) {
+       case MLX5_PORT_CHANGE_SUBTYPE_DOWN:
+               return MLX5_DEV_EVENT_PORT_DOWN;
+       case MLX5_PORT_CHANGE_SUBTYPE_ACTIVE:
+               return MLX5_DEV_EVENT_PORT_UP;
+       case MLX5_PORT_CHANGE_SUBTYPE_INITIALIZED:
+               return MLX5_DEV_EVENT_PORT_INITIALIZED;
+       case MLX5_PORT_CHANGE_SUBTYPE_LID:
+               return MLX5_DEV_EVENT_LID_CHANGE;
+       case MLX5_PORT_CHANGE_SUBTYPE_PKEY:
+               return MLX5_DEV_EVENT_PKEY_CHANGE;
+       case MLX5_PORT_CHANGE_SUBTYPE_GUID:
+               return MLX5_DEV_EVENT_GUID_CHANGE;
+       case MLX5_PORT_CHANGE_SUBTYPE_CLIENT_REREG:
+               return MLX5_DEV_EVENT_CLIENT_REREG;
+       }
+       return -1;
+}
+
+static void eq_update_ci(struct mlx5_eq *eq, int arm)
+{
+       __be32 __iomem *addr = eq->doorbell + (arm ? 0 : 2);
+       u32 val = (eq->cons_index & 0xffffff) | (eq->eqn << 24);
+       __raw_writel((__force u32) cpu_to_be32(val), addr);
+       /* We still want ordering, just not swabbing, so add a barrier */
+       mb();
+}
+
+static int mlx5_eq_int(struct mlx5_core_dev *dev, struct mlx5_eq *eq)
+{
+       struct mlx5_eqe *eqe;
+       int eqes_found = 0;
+       int set_ci = 0;
+       u32 cqn;
+       u32 rsn;
+       u8 port;
+
+       while ((eqe = next_eqe_sw(eq))) {
+               /*
+                * Make sure we read EQ entry contents after we've
+                * checked the ownership bit.
+                */
+               dma_rmb();
+
+               mlx5_core_dbg(eq->dev, "eqn %d, eqe type %s\n",
+                             eq->eqn, eqe_type_str(eqe->type));
+               switch (eqe->type) {
+               case MLX5_EVENT_TYPE_COMP:
+                       cqn = be32_to_cpu(eqe->data.comp.cqn) & 0xffffff;
+                       mlx5_cq_completion(dev, cqn);
+                       break;
+
+               case MLX5_EVENT_TYPE_PATH_MIG:
+               case MLX5_EVENT_TYPE_COMM_EST:
+               case MLX5_EVENT_TYPE_SQ_DRAINED:
+               case MLX5_EVENT_TYPE_SRQ_LAST_WQE:
+               case MLX5_EVENT_TYPE_WQ_CATAS_ERROR:
+               case MLX5_EVENT_TYPE_PATH_MIG_FAILED:
+               case MLX5_EVENT_TYPE_WQ_INVAL_REQ_ERROR:
+               case MLX5_EVENT_TYPE_WQ_ACCESS_ERROR:
+                       rsn = be32_to_cpu(eqe->data.qp_srq.qp_srq_n) & 0xffffff;
+                       mlx5_core_dbg(dev, "event %s(%d) arrived on resource 0x%x\n",
+                                     eqe_type_str(eqe->type), eqe->type, rsn);
+                       mlx5_rsc_event(dev, rsn, eqe->type);
+                       break;
+
+               case MLX5_EVENT_TYPE_SRQ_RQ_LIMIT:
+               case MLX5_EVENT_TYPE_SRQ_CATAS_ERROR:
+                       rsn = be32_to_cpu(eqe->data.qp_srq.qp_srq_n) & 0xffffff;
+                       mlx5_core_dbg(dev, "SRQ event %s(%d): srqn 0x%x\n",
+                                     eqe_type_str(eqe->type), eqe->type, rsn);
+                       mlx5_srq_event(dev, rsn, eqe->type);
+                       break;
+
+               case MLX5_EVENT_TYPE_CMD:
+                       mlx5_cmd_comp_handler(dev, be32_to_cpu(eqe->data.cmd.vector));
+                       break;
+
+               case MLX5_EVENT_TYPE_PORT_CHANGE:
+                       port = (eqe->data.port.port >> 4) & 0xf;
+                       switch (eqe->sub_type) {
+                       case MLX5_PORT_CHANGE_SUBTYPE_DOWN:
+                       case MLX5_PORT_CHANGE_SUBTYPE_ACTIVE:
+                       case MLX5_PORT_CHANGE_SUBTYPE_LID:
+                       case MLX5_PORT_CHANGE_SUBTYPE_PKEY:
+                       case MLX5_PORT_CHANGE_SUBTYPE_GUID:
+                       case MLX5_PORT_CHANGE_SUBTYPE_CLIENT_REREG:
+                       case MLX5_PORT_CHANGE_SUBTYPE_INITIALIZED:
+                               if (dev->event)
+                                       dev->event(dev, port_subtype_event(eqe->sub_type),
+                                                  (unsigned long)port);
+                               break;
+                       default:
+                               mlx5_core_warn(dev, "Port event with unrecognized subtype: port %d, sub_type %d\n",
+                                              port, eqe->sub_type);
+                       }
+                       break;
+               case MLX5_EVENT_TYPE_CQ_ERROR:
+                       cqn = be32_to_cpu(eqe->data.cq_err.cqn) & 0xffffff;
+                       mlx5_core_warn(dev, "CQ error on CQN 0x%x, syndrom 0x%x\n",
+                                      cqn, eqe->data.cq_err.syndrome);
+                       mlx5_cq_event(dev, cqn, eqe->type);
+                       break;
+
+               case MLX5_EVENT_TYPE_PAGE_REQUEST:
+                       {
+                               u16 func_id = be16_to_cpu(eqe->data.req_pages.func_id);
+                               s32 npages = be32_to_cpu(eqe->data.req_pages.num_pages);
+
+                               mlx5_core_dbg(dev, "page request for func 0x%x, npages %d\n",
+                                             func_id, npages);
+                               mlx5_core_req_pages_handler(dev, func_id, npages);
+                       }
+                       break;
+
+#ifdef CONFIG_INFINIBAND_ON_DEMAND_PAGING
+               case MLX5_EVENT_TYPE_PAGE_FAULT:
+                       mlx5_eq_pagefault(dev, eqe);
+                       break;
+#endif
+
+               default:
+                       mlx5_core_warn(dev, "Unhandled event 0x%x on EQ 0x%x\n",
+                                      eqe->type, eq->eqn);
+                       break;
+               }
+
+               ++eq->cons_index;
+               eqes_found = 1;
+               ++set_ci;
+
+               /* The HCA will think the queue has overflowed if we
+                * don't tell it we've been processing events.  We
+                * create our EQs with MLX5_NUM_SPARE_EQE extra
+                * entries, so we must update our consumer index at
+                * least that often.
+                */
+               if (unlikely(set_ci >= MLX5_NUM_SPARE_EQE)) {
+                       eq_update_ci(eq, 0);
+                       set_ci = 0;
+               }
+       }
+
+       eq_update_ci(eq, 1);
+
+       return eqes_found;
+}
+
+static irqreturn_t mlx5_msix_handler(int irq, void *eq_ptr)
+{
+       struct mlx5_eq *eq = eq_ptr;
+       struct mlx5_core_dev *dev = eq->dev;
+
+       mlx5_eq_int(dev, eq);
+
+       /* MSI-X vectors always belong to us */
+       return IRQ_HANDLED;
+}
+
+static void init_eq_buf(struct mlx5_eq *eq)
+{
+       struct mlx5_eqe *eqe;
+       int i;
+
+       for (i = 0; i < eq->nent; i++) {
+               eqe = get_eqe(eq, i);
+               eqe->owner = MLX5_EQE_OWNER_INIT_VAL;
+       }
+}
+
+int mlx5_create_map_eq(struct mlx5_core_dev *dev, struct mlx5_eq *eq, u8 vecidx,
+                      int nent, u64 mask, const char *name, struct mlx5_uar *uar)
+{
+       struct mlx5_eq_table *table = &dev->priv.eq_table;
+       struct mlx5_create_eq_mbox_in *in;
+       struct mlx5_create_eq_mbox_out out;
+       int err;
+       int inlen;
+
+       eq->nent = roundup_pow_of_two(nent + MLX5_NUM_SPARE_EQE);
+       err = mlx5_buf_alloc(dev, eq->nent * MLX5_EQE_SIZE, 2 * PAGE_SIZE,
+                            &eq->buf);
+       if (err)
+               return err;
+
+       init_eq_buf(eq);
+
+       inlen = sizeof(*in) + sizeof(in->pas[0]) * eq->buf.npages;
+       in = mlx5_vzalloc(inlen);
+       if (!in) {
+               err = -ENOMEM;
+               goto err_buf;
+       }
+       memset(&out, 0, sizeof(out));
+
+       mlx5_fill_page_array(&eq->buf, in->pas);
+
+       in->hdr.opcode = cpu_to_be16(MLX5_CMD_OP_CREATE_EQ);
+       in->ctx.log_sz_usr_page = cpu_to_be32(ilog2(eq->nent) << 24 | uar->index);
+       in->ctx.intr = vecidx;
+       in->ctx.log_page_size = eq->buf.page_shift - MLX5_ADAPTER_PAGE_SHIFT;
+       in->events_mask = cpu_to_be64(mask);
+
+       err = mlx5_cmd_exec(dev, in, inlen, &out, sizeof(out));
+       if (err)
+               goto err_in;
+
+       if (out.hdr.status) {
+               err = mlx5_cmd_status_to_err(&out.hdr);
+               goto err_in;
+       }
+
+       snprintf(eq->name, MLX5_MAX_EQ_NAME, "%s@pci:%s",
+                name, pci_name(dev->pdev));
+       eq->eqn = out.eq_number;
+       eq->irqn = vecidx;
+       eq->dev = dev;
+       eq->doorbell = uar->map + MLX5_EQ_DOORBEL_OFFSET;
+       err = request_irq(table->msix_arr[vecidx].vector, mlx5_msix_handler, 0,
+                         eq->name, eq);
+       if (err)
+               goto err_eq;
+
+       err = mlx5_debug_eq_add(dev, eq);
+       if (err)
+               goto err_irq;
+
+       /* EQs are created in ARMED state
+        */
+       eq_update_ci(eq, 1);
+
+       kvfree(in);
+       return 0;
+
+err_irq:
+       free_irq(table->msix_arr[vecidx].vector, eq);
+
+err_eq:
+       mlx5_cmd_destroy_eq(dev, eq->eqn);
+
+err_in:
+       kvfree(in);
+
+err_buf:
+       mlx5_buf_free(dev, &eq->buf);
+       return err;
+}
+EXPORT_SYMBOL_GPL(mlx5_create_map_eq);
+
+int mlx5_destroy_unmap_eq(struct mlx5_core_dev *dev, struct mlx5_eq *eq)
+{
+       struct mlx5_eq_table *table = &dev->priv.eq_table;
+       int err;
+
+       mlx5_debug_eq_remove(dev, eq);
+       free_irq(table->msix_arr[eq->irqn].vector, eq);
+       err = mlx5_cmd_destroy_eq(dev, eq->eqn);
+       if (err)
+               mlx5_core_warn(dev, "failed to destroy a previously created eq: eqn %d\n",
+                              eq->eqn);
+       synchronize_irq(table->msix_arr[eq->irqn].vector);
+       mlx5_buf_free(dev, &eq->buf);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(mlx5_destroy_unmap_eq);
+
+int mlx5_eq_init(struct mlx5_core_dev *dev)
+{
+       int err;
+
+       spin_lock_init(&dev->priv.eq_table.lock);
+
+       err = mlx5_eq_debugfs_init(dev);
+
+       return err;
+}
+
+
+void mlx5_eq_cleanup(struct mlx5_core_dev *dev)
+{
+       mlx5_eq_debugfs_cleanup(dev);
+}
+
+int mlx5_start_eqs(struct mlx5_core_dev *dev)
+{
+       struct mlx5_eq_table *table = &dev->priv.eq_table;
+       u32 async_event_mask = MLX5_ASYNC_EVENT_MASK;
+       int err;
+
+       if (dev->caps.gen.flags & MLX5_DEV_CAP_FLAG_ON_DMND_PG)
+               async_event_mask |= (1ull << MLX5_EVENT_TYPE_PAGE_FAULT);
+
+       err = mlx5_create_map_eq(dev, &table->cmd_eq, MLX5_EQ_VEC_CMD,
+                                MLX5_NUM_CMD_EQE, 1ull << MLX5_EVENT_TYPE_CMD,
+                                "mlx5_cmd_eq", &dev->priv.uuari.uars[0]);
+       if (err) {
+               mlx5_core_warn(dev, "failed to create cmd EQ %d\n", err);
+               return err;
+       }
+
+       mlx5_cmd_use_events(dev);
+
+       err = mlx5_create_map_eq(dev, &table->async_eq, MLX5_EQ_VEC_ASYNC,
+                                MLX5_NUM_ASYNC_EQE, async_event_mask,
+                                "mlx5_async_eq", &dev->priv.uuari.uars[0]);
+       if (err) {
+               mlx5_core_warn(dev, "failed to create async EQ %d\n", err);
+               goto err1;
+       }
+
+       err = mlx5_create_map_eq(dev, &table->pages_eq,
+                                MLX5_EQ_VEC_PAGES,
+                                dev->caps.gen.max_vf + 1,
+                                1 << MLX5_EVENT_TYPE_PAGE_REQUEST, "mlx5_pages_eq",
+                                &dev->priv.uuari.uars[0]);
+       if (err) {
+               mlx5_core_warn(dev, "failed to create pages EQ %d\n", err);
+               goto err2;
+       }
+
+       return err;
+
+err2:
+       mlx5_destroy_unmap_eq(dev, &table->async_eq);
+
+err1:
+       mlx5_cmd_use_polling(dev);
+       mlx5_destroy_unmap_eq(dev, &table->cmd_eq);
+       return err;
+}
+
+int mlx5_stop_eqs(struct mlx5_core_dev *dev)
+{
+       struct mlx5_eq_table *table = &dev->priv.eq_table;
+       int err;
+
+       err = mlx5_destroy_unmap_eq(dev, &table->pages_eq);
+       if (err)
+               return err;
+
+       mlx5_destroy_unmap_eq(dev, &table->async_eq);
+       mlx5_cmd_use_polling(dev);
+
+       err = mlx5_destroy_unmap_eq(dev, &table->cmd_eq);
+       if (err)
+               mlx5_cmd_use_events(dev);
+
+       return err;
+}
+
+int mlx5_core_eq_query(struct mlx5_core_dev *dev, struct mlx5_eq *eq,
+                      struct mlx5_query_eq_mbox_out *out, int outlen)
+{
+       struct mlx5_query_eq_mbox_in in;
+       int err;
+
+       memset(&in, 0, sizeof(in));
+       memset(out, 0, outlen);
+       in.hdr.opcode = cpu_to_be16(MLX5_CMD_OP_QUERY_EQ);
+       in.eqn = eq->eqn;
+       err = mlx5_cmd_exec(dev, &in, sizeof(in), out, outlen);
+       if (err)
+               return err;
+
+       if (out->hdr.status)
+               err = mlx5_cmd_status_to_err(&out->hdr);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(mlx5_core_eq_query);