qed: Add doorbell overflow recovery mechanism
authorAriel Elior <Ariel.Elior@cavium.com>
Wed, 28 Nov 2018 16:16:02 +0000 (18:16 +0200)
committerDavid S. Miller <davem@davemloft.net>
Fri, 30 Nov 2018 21:45:12 +0000 (13:45 -0800)
Add the database used to register doorbelling entities, and APIs for adding
and deleting entries, and logic for traversing the database and doorbelling
once on behalf of all entities.

Signed-off-by: Ariel Elior <Ariel.Elior@cavium.com>
Signed-off-by: Michal Kalderon <Michal.Kalderon@cavium.com>
Signed-off-by: Tomer Tayar <Tomer.Tayar@cavium.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qlogic/qed/qed.h
drivers/net/ethernet/qlogic/qed/qed_dev.c
drivers/net/ethernet/qlogic/qed/qed_dev_api.h
include/linux/qed/qed_if.h

index d9a03aba0e024156a1c214c8ba7697edb1203d1c..fb399ee681d37852d462e3880a6954395e5a6296 100644 (file)
@@ -296,6 +296,12 @@ enum qed_wol_support {
        QED_WOL_SUPPORT_PME,
 };
 
+enum qed_db_rec_exec {
+       DB_REC_DRY_RUN,
+       DB_REC_REAL_DEAL,
+       DB_REC_ONCE,
+};
+
 struct qed_hw_info {
        /* PCI personality */
        enum qed_pci_personality personality;
@@ -425,6 +431,14 @@ struct qed_qm_info {
        u8 num_pf_rls;
 };
 
+struct qed_db_recovery_info {
+       struct list_head list;
+
+       /* Lock to protect the doorbell recovery mechanism list */
+       spinlock_t lock;
+       u32 db_recovery_counter;
+};
+
 struct storm_stats {
        u32     address;
        u32     len;
@@ -640,6 +654,9 @@ struct qed_hwfn {
        /* L2-related */
        struct qed_l2_info *p_l2_info;
 
+       /* Mechanism for recovering from doorbell drop */
+       struct qed_db_recovery_info db_recovery_info;
+
        /* Nvm images number and attributes */
        struct qed_nvm_image_info nvm_info;
 
index 88a8576ca9ceae1b4adc69b8702f1b626e82e54f..19b8a6d7283276dfa9076b212387a6d7d4158cf5 100644 (file)
 
 static DEFINE_SPINLOCK(qm_lock);
 
+/******************** Doorbell Recovery *******************/
+/* The doorbell recovery mechanism consists of a list of entries which represent
+ * doorbelling entities (l2 queues, roce sq/rq/cqs, the slowpath spq, etc). Each
+ * entity needs to register with the mechanism and provide the parameters
+ * describing it's doorbell, including a location where last used doorbell data
+ * can be found. The doorbell execute function will traverse the list and
+ * doorbell all of the registered entries.
+ */
+struct qed_db_recovery_entry {
+       struct list_head list_entry;
+       void __iomem *db_addr;
+       void *db_data;
+       enum qed_db_rec_width db_width;
+       enum qed_db_rec_space db_space;
+       u8 hwfn_idx;
+};
+
+/* Display a single doorbell recovery entry */
+static void qed_db_recovery_dp_entry(struct qed_hwfn *p_hwfn,
+                                    struct qed_db_recovery_entry *db_entry,
+                                    char *action)
+{
+       DP_VERBOSE(p_hwfn,
+                  QED_MSG_SPQ,
+                  "(%s: db_entry %p, addr %p, data %p, width %s, %s space, hwfn %d)\n",
+                  action,
+                  db_entry,
+                  db_entry->db_addr,
+                  db_entry->db_data,
+                  db_entry->db_width == DB_REC_WIDTH_32B ? "32b" : "64b",
+                  db_entry->db_space == DB_REC_USER ? "user" : "kernel",
+                  db_entry->hwfn_idx);
+}
+
+/* Doorbell address sanity (address within doorbell bar range) */
+static bool qed_db_rec_sanity(struct qed_dev *cdev,
+                             void __iomem *db_addr, void *db_data)
+{
+       /* Make sure doorbell address is within the doorbell bar */
+       if (db_addr < cdev->doorbells ||
+           (u8 __iomem *)db_addr >
+           (u8 __iomem *)cdev->doorbells + cdev->db_size) {
+               WARN(true,
+                    "Illegal doorbell address: %p. Legal range for doorbell addresses is [%p..%p]\n",
+                    db_addr,
+                    cdev->doorbells,
+                    (u8 __iomem *)cdev->doorbells + cdev->db_size);
+               return false;
+       }
+
+       /* ake sure doorbell data pointer is not null */
+       if (!db_data) {
+               WARN(true, "Illegal doorbell data pointer: %p", db_data);
+               return false;
+       }
+
+       return true;
+}
+
+/* Find hwfn according to the doorbell address */
+static struct qed_hwfn *qed_db_rec_find_hwfn(struct qed_dev *cdev,
+                                            void __iomem *db_addr)
+{
+       struct qed_hwfn *p_hwfn;
+
+       /* In CMT doorbell bar is split down the middle between engine 0 and enigne 1 */
+       if (cdev->num_hwfns > 1)
+               p_hwfn = db_addr < cdev->hwfns[1].doorbells ?
+                   &cdev->hwfns[0] : &cdev->hwfns[1];
+       else
+               p_hwfn = QED_LEADING_HWFN(cdev);
+
+       return p_hwfn;
+}
+
+/* Add a new entry to the doorbell recovery mechanism */
+int qed_db_recovery_add(struct qed_dev *cdev,
+                       void __iomem *db_addr,
+                       void *db_data,
+                       enum qed_db_rec_width db_width,
+                       enum qed_db_rec_space db_space)
+{
+       struct qed_db_recovery_entry *db_entry;
+       struct qed_hwfn *p_hwfn;
+
+       /* Shortcircuit VFs, for now */
+       if (IS_VF(cdev)) {
+               DP_VERBOSE(cdev,
+                          QED_MSG_IOV, "db recovery - skipping VF doorbell\n");
+               return 0;
+       }
+
+       /* Sanitize doorbell address */
+       if (!qed_db_rec_sanity(cdev, db_addr, db_data))
+               return -EINVAL;
+
+       /* Obtain hwfn from doorbell address */
+       p_hwfn = qed_db_rec_find_hwfn(cdev, db_addr);
+
+       /* Create entry */
+       db_entry = kzalloc(sizeof(*db_entry), GFP_KERNEL);
+       if (!db_entry) {
+               DP_NOTICE(cdev, "Failed to allocate a db recovery entry\n");
+               return -ENOMEM;
+       }
+
+       /* Populate entry */
+       db_entry->db_addr = db_addr;
+       db_entry->db_data = db_data;
+       db_entry->db_width = db_width;
+       db_entry->db_space = db_space;
+       db_entry->hwfn_idx = p_hwfn->my_id;
+
+       /* Display */
+       qed_db_recovery_dp_entry(p_hwfn, db_entry, "Adding");
+
+       /* Protect the list */
+       spin_lock_bh(&p_hwfn->db_recovery_info.lock);
+       list_add_tail(&db_entry->list_entry, &p_hwfn->db_recovery_info.list);
+       spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
+
+       return 0;
+}
+
+/* Remove an entry from the doorbell recovery mechanism */
+int qed_db_recovery_del(struct qed_dev *cdev,
+                       void __iomem *db_addr, void *db_data)
+{
+       struct qed_db_recovery_entry *db_entry = NULL;
+       struct qed_hwfn *p_hwfn;
+       int rc = -EINVAL;
+
+       /* Shortcircuit VFs, for now */
+       if (IS_VF(cdev)) {
+               DP_VERBOSE(cdev,
+                          QED_MSG_IOV, "db recovery - skipping VF doorbell\n");
+               return 0;
+       }
+
+       /* Sanitize doorbell address */
+       if (!qed_db_rec_sanity(cdev, db_addr, db_data))
+               return -EINVAL;
+
+       /* Obtain hwfn from doorbell address */
+       p_hwfn = qed_db_rec_find_hwfn(cdev, db_addr);
+
+       /* Protect the list */
+       spin_lock_bh(&p_hwfn->db_recovery_info.lock);
+       list_for_each_entry(db_entry,
+                           &p_hwfn->db_recovery_info.list, list_entry) {
+               /* search according to db_data addr since db_addr is not unique (roce) */
+               if (db_entry->db_data == db_data) {
+                       qed_db_recovery_dp_entry(p_hwfn, db_entry, "Deleting");
+                       list_del(&db_entry->list_entry);
+                       rc = 0;
+                       break;
+               }
+       }
+
+       spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
+
+       if (rc == -EINVAL)
+
+               DP_NOTICE(p_hwfn,
+                         "Failed to find element in list. Key (db_data addr) was %p. db_addr was %p\n",
+                         db_data, db_addr);
+       else
+               kfree(db_entry);
+
+       return rc;
+}
+
+/* Initialize the doorbell recovery mechanism */
+static int qed_db_recovery_setup(struct qed_hwfn *p_hwfn)
+{
+       DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "Setting up db recovery\n");
+
+       /* Make sure db_size was set in cdev */
+       if (!p_hwfn->cdev->db_size) {
+               DP_ERR(p_hwfn->cdev, "db_size not set\n");
+               return -EINVAL;
+       }
+
+       INIT_LIST_HEAD(&p_hwfn->db_recovery_info.list);
+       spin_lock_init(&p_hwfn->db_recovery_info.lock);
+       p_hwfn->db_recovery_info.db_recovery_counter = 0;
+
+       return 0;
+}
+
+/* Destroy the doorbell recovery mechanism */
+static void qed_db_recovery_teardown(struct qed_hwfn *p_hwfn)
+{
+       struct qed_db_recovery_entry *db_entry = NULL;
+
+       DP_VERBOSE(p_hwfn, QED_MSG_SPQ, "Tearing down db recovery\n");
+       if (!list_empty(&p_hwfn->db_recovery_info.list)) {
+               DP_VERBOSE(p_hwfn,
+                          QED_MSG_SPQ,
+                          "Doorbell Recovery teardown found the doorbell recovery list was not empty (Expected in disorderly driver unload (e.g. recovery) otherwise this probably means some flow forgot to db_recovery_del). Prepare to purge doorbell recovery list...\n");
+               while (!list_empty(&p_hwfn->db_recovery_info.list)) {
+                       db_entry =
+                           list_first_entry(&p_hwfn->db_recovery_info.list,
+                                            struct qed_db_recovery_entry,
+                                            list_entry);
+                       qed_db_recovery_dp_entry(p_hwfn, db_entry, "Purging");
+                       list_del(&db_entry->list_entry);
+                       kfree(db_entry);
+               }
+       }
+       p_hwfn->db_recovery_info.db_recovery_counter = 0;
+}
+
+/* Print the content of the doorbell recovery mechanism */
+void qed_db_recovery_dp(struct qed_hwfn *p_hwfn)
+{
+       struct qed_db_recovery_entry *db_entry = NULL;
+
+       DP_NOTICE(p_hwfn,
+                 "Dispalying doorbell recovery database. Counter was %d\n",
+                 p_hwfn->db_recovery_info.db_recovery_counter);
+
+       /* Protect the list */
+       spin_lock_bh(&p_hwfn->db_recovery_info.lock);
+       list_for_each_entry(db_entry,
+                           &p_hwfn->db_recovery_info.list, list_entry) {
+               qed_db_recovery_dp_entry(p_hwfn, db_entry, "Printing");
+       }
+
+       spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
+}
+
+/* Ring the doorbell of a single doorbell recovery entry */
+static void qed_db_recovery_ring(struct qed_hwfn *p_hwfn,
+                                struct qed_db_recovery_entry *db_entry,
+                                enum qed_db_rec_exec db_exec)
+{
+       if (db_exec != DB_REC_ONCE) {
+               /* Print according to width */
+               if (db_entry->db_width == DB_REC_WIDTH_32B) {
+                       DP_VERBOSE(p_hwfn, QED_MSG_SPQ,
+                                  "%s doorbell address %p data %x\n",
+                                  db_exec == DB_REC_DRY_RUN ?
+                                  "would have rung" : "ringing",
+                                  db_entry->db_addr,
+                                  *(u32 *)db_entry->db_data);
+               } else {
+                       DP_VERBOSE(p_hwfn, QED_MSG_SPQ,
+                                  "%s doorbell address %p data %llx\n",
+                                  db_exec == DB_REC_DRY_RUN ?
+                                  "would have rung" : "ringing",
+                                  db_entry->db_addr,
+                                  *(u64 *)(db_entry->db_data));
+               }
+       }
+
+       /* Sanity */
+       if (!qed_db_rec_sanity(p_hwfn->cdev, db_entry->db_addr,
+                              db_entry->db_data))
+               return;
+
+       /* Flush the write combined buffer. Since there are multiple doorbelling
+        * entities using the same address, if we don't flush, a transaction
+        * could be lost.
+        */
+       wmb();
+
+       /* Ring the doorbell */
+       if (db_exec == DB_REC_REAL_DEAL || db_exec == DB_REC_ONCE) {
+               if (db_entry->db_width == DB_REC_WIDTH_32B)
+                       DIRECT_REG_WR(db_entry->db_addr,
+                                     *(u32 *)(db_entry->db_data));
+               else
+                       DIRECT_REG_WR64(db_entry->db_addr,
+                                       *(u64 *)(db_entry->db_data));
+       }
+
+       /* Flush the write combined buffer. Next doorbell may come from a
+        * different entity to the same address...
+        */
+       wmb();
+}
+
+/* Traverse the doorbell recovery entry list and ring all the doorbells */
+void qed_db_recovery_execute(struct qed_hwfn *p_hwfn,
+                            enum qed_db_rec_exec db_exec)
+{
+       struct qed_db_recovery_entry *db_entry = NULL;
+
+       if (db_exec != DB_REC_ONCE) {
+               DP_NOTICE(p_hwfn,
+                         "Executing doorbell recovery. Counter was %d\n",
+                         p_hwfn->db_recovery_info.db_recovery_counter);
+
+               /* Track amount of times recovery was executed */
+               p_hwfn->db_recovery_info.db_recovery_counter++;
+       }
+
+       /* Protect the list */
+       spin_lock_bh(&p_hwfn->db_recovery_info.lock);
+       list_for_each_entry(db_entry,
+                           &p_hwfn->db_recovery_info.list, list_entry) {
+               qed_db_recovery_ring(p_hwfn, db_entry, db_exec);
+               if (db_exec == DB_REC_ONCE)
+                       break;
+       }
+
+       spin_unlock_bh(&p_hwfn->db_recovery_info.lock);
+}
+
+/******************** Doorbell Recovery end ****************/
+
 #define QED_MIN_DPIS            (4)
 #define QED_MIN_PWM_REGION      (QED_WID_SIZE * QED_MIN_DPIS)
 
@@ -194,6 +506,9 @@ void qed_resc_free(struct qed_dev *cdev)
                qed_dmae_info_free(p_hwfn);
                qed_dcbx_info_free(p_hwfn);
                qed_dbg_user_data_free(p_hwfn);
+
+               /* Destroy doorbell recovery mechanism */
+               qed_db_recovery_teardown(p_hwfn);
        }
 }
 
@@ -969,6 +1284,11 @@ int qed_resc_alloc(struct qed_dev *cdev)
                struct qed_hwfn *p_hwfn = &cdev->hwfns[i];
                u32 n_eqes, num_cons;
 
+               /* Initialize the doorbell recovery mechanism */
+               rc = qed_db_recovery_setup(p_hwfn);
+               if (rc)
+                       goto alloc_err;
+
                /* First allocate the context manager structure */
                rc = qed_cxt_mngr_alloc(p_hwfn);
                if (rc)
index defdda1ffaa239bb687457000f82431eac4e0345..acccd85170aa22484610d852ddc6d7ed4f4fb097 100644 (file)
@@ -472,6 +472,34 @@ int qed_get_queue_coalesce(struct qed_hwfn *p_hwfn, u16 *coal, void *handle);
 int
 qed_set_queue_coalesce(u16 rx_coal, u16 tx_coal, void *p_handle);
 
+/**
+ * @brief db_recovery_add - add doorbell information to the doorbell
+ * recovery mechanism.
+ *
+ * @param cdev
+ * @param db_addr - doorbell address
+ * @param db_data - address of where db_data is stored
+ * @param db_width - doorbell is 32b pr 64b
+ * @param db_space - doorbell recovery addresses are user or kernel space
+ */
+int qed_db_recovery_add(struct qed_dev *cdev,
+                       void __iomem *db_addr,
+                       void *db_data,
+                       enum qed_db_rec_width db_width,
+                       enum qed_db_rec_space db_space);
+
+/**
+ * @brief db_recovery_del - remove doorbell information from the doorbell
+ * recovery mechanism. db_data serves as key (db_addr is not unique).
+ *
+ * @param cdev
+ * @param db_addr - doorbell address
+ * @param db_data - address where db_data is stored. Serves as key for the
+ *                  entry to delete.
+ */
+int qed_db_recovery_del(struct qed_dev *cdev,
+                       void __iomem *db_addr, void *db_data);
+
 
 const char *qed_hw_get_resc_name(enum qed_resources res_id);
 #endif
index a47321a0d5727713ae8d3ab33bf2851266fc9f4d..eb851f89f417ab3bc8c268ab9ef927278620eabb 100644 (file)
@@ -47,6 +47,7 @@
 #include <linux/slab.h>
 #include <linux/qed/common_hsi.h>
 #include <linux/qed/qed_chain.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
 
 enum dcbx_protocol_type {
        DCBX_PROTOCOL_ISCSI,
@@ -448,11 +449,24 @@ struct qed_mfw_tlv_iscsi {
        bool tx_bytes_set;
 };
 
+enum qed_db_rec_width {
+       DB_REC_WIDTH_32B,
+       DB_REC_WIDTH_64B,
+};
+
+enum qed_db_rec_space {
+       DB_REC_KERNEL,
+       DB_REC_USER,
+};
+
 #define DIRECT_REG_WR(reg_addr, val) writel((u32)val, \
                                            (void __iomem *)(reg_addr))
 
 #define DIRECT_REG_RD(reg_addr) readl((void __iomem *)(reg_addr))
 
+#define DIRECT_REG_WR64(reg_addr, val) writeq((u32)val,        \
+                                             (void __iomem *)(reg_addr))
+
 #define QED_COALESCE_MAX 0x1FF
 #define QED_DEFAULT_RX_USECS 12
 #define QED_DEFAULT_TX_USECS 48