/* Copyright(c) 2018-2019  Realtek Corporation
  */
 
+#include <linux/devcoredump.h>
+
 #include "main.h"
 #include "regd.h"
 #include "fw.h"
                 sta->addr, si->mac_id);
 }
 
-static bool rtw_fw_dump_crash_log(struct rtw_dev *rtwdev)
+struct rtw_fwcd_hdr {
+       u32 item;
+       u32 size;
+       u32 padding1;
+       u32 padding2;
+} __packed;
+
+static int rtw_fwcd_prep(struct rtw_dev *rtwdev)
+{
+       struct rtw_chip_info *chip = rtwdev->chip;
+       struct rtw_fwcd_desc *desc = &rtwdev->fw.fwcd_desc;
+       const struct rtw_fwcd_segs *segs = chip->fwcd_segs;
+       u32 prep_size = chip->fw_rxff_size + sizeof(struct rtw_fwcd_hdr);
+       u8 i;
+
+       if (segs) {
+               prep_size += segs->num * sizeof(struct rtw_fwcd_hdr);
+
+               for (i = 0; i < segs->num; i++)
+                       prep_size += segs->segs[i];
+       }
+
+       desc->data = vmalloc(prep_size);
+       if (!desc->data)
+               return -ENOMEM;
+
+       desc->size = prep_size;
+       desc->next = desc->data;
+
+       return 0;
+}
+
+static u8 *rtw_fwcd_next(struct rtw_dev *rtwdev, u32 item, u32 size)
+{
+       struct rtw_fwcd_desc *desc = &rtwdev->fw.fwcd_desc;
+       struct rtw_fwcd_hdr *hdr;
+       u8 *next;
+
+       if (!desc->data) {
+               rtw_dbg(rtwdev, RTW_DBG_FW, "fwcd isn't prepared successfully\n");
+               return NULL;
+       }
+
+       next = desc->next + sizeof(struct rtw_fwcd_hdr);
+       if (next - desc->data + size > desc->size) {
+               rtw_dbg(rtwdev, RTW_DBG_FW, "fwcd isn't prepared enough\n");
+               return NULL;
+       }
+
+       hdr = (struct rtw_fwcd_hdr *)(desc->next);
+       hdr->item = item;
+       hdr->size = size;
+       hdr->padding1 = 0x01234567;
+       hdr->padding2 = 0x89abcdef;
+       desc->next = next + size;
+
+       return next;
+}
+
+static void rtw_fwcd_dump(struct rtw_dev *rtwdev)
+{
+       struct rtw_fwcd_desc *desc = &rtwdev->fw.fwcd_desc;
+
+       rtw_dbg(rtwdev, RTW_DBG_FW, "dump fwcd\n");
+
+       /* Data will be freed after lifetime of device coredump. After calling
+        * dev_coredump, data is supposed to be handled by the device coredump
+        * framework. Note that a new dump will be discarded if a previous one
+        * hasn't been released yet.
+        */
+       dev_coredumpv(rtwdev->dev, desc->data, desc->size, GFP_KERNEL);
+}
+
+static void rtw_fwcd_free(struct rtw_dev *rtwdev, bool free_self)
+{
+       struct rtw_fwcd_desc *desc = &rtwdev->fw.fwcd_desc;
+
+       if (free_self) {
+               rtw_dbg(rtwdev, RTW_DBG_FW, "free fwcd by self\n");
+               vfree(desc->data);
+       }
+
+       desc->data = NULL;
+       desc->next = NULL;
+}
+
+static int rtw_fw_dump_crash_log(struct rtw_dev *rtwdev)
 {
        u32 size = rtwdev->chip->fw_rxff_size;
        u32 *buf;
        u8 seq;
-       bool ret = true;
 
-       buf = vmalloc(size);
+       buf = (u32 *)rtw_fwcd_next(rtwdev, RTW_FWCD_TLV, size);
        if (!buf)
-               goto exit;
+               return -ENOMEM;
 
        if (rtw_fw_dump_fifo(rtwdev, RTW_FW_FIFO_SEL_RXBUF_FW, 0, size, buf)) {
                rtw_dbg(rtwdev, RTW_DBG_FW, "dump fw fifo fail\n");
-               goto free_buf;
+               return -EINVAL;
        }
 
        if (GET_FW_DUMP_LEN(buf) == 0) {
                rtw_dbg(rtwdev, RTW_DBG_FW, "fw crash dump's length is 0\n");
-               goto free_buf;
+               return -EINVAL;
        }
 
        seq = GET_FW_DUMP_SEQ(buf);
-       if (seq > 0 && seq != (rtwdev->fw.prev_dump_seq + 1)) {
+       if (seq > 0) {
                rtw_dbg(rtwdev, RTW_DBG_FW,
                        "fw crash dump's seq is wrong: %d\n", seq);
-               goto free_buf;
-       }
-
-       print_hex_dump(KERN_ERR, "rtw88 fw dump: ", DUMP_PREFIX_OFFSET, 16, 1,
-                      buf, size, true);
-
-       if (GET_FW_DUMP_MORE(buf) == 1) {
-               rtwdev->fw.prev_dump_seq = seq;
-               ret = false;
+               return -EINVAL;
        }
 
-free_buf:
-       vfree(buf);
-exit:
-       rtw_write8(rtwdev, REG_MCU_TST_CFG, 0);
-
-       return ret;
+       return 0;
 }
 
 int rtw_dump_fw(struct rtw_dev *rtwdev, const u32 ocp_src, u32 size,
-               const char *prefix_str)
+               u32 fwcd_item)
 {
        u32 rxff = rtwdev->chip->fw_rxff_size;
        u32 dump_size, done_size = 0;
        u8 *buf;
        int ret;
 
-       buf = vzalloc(size);
+       buf = rtw_fwcd_next(rtwdev, fwcd_item, size);
        if (!buf)
                return -ENOMEM;
 
                        rtw_err(rtwdev,
                                "ddma fw 0x%x [+0x%x] to fw fifo fail\n",
                                ocp_src, done_size);
-                       goto exit;
+                       return ret;
                }
 
                ret = rtw_fw_dump_fifo(rtwdev, RTW_FW_FIFO_SEL_RXBUF_FW, 0,
                        rtw_err(rtwdev,
                                "dump fw 0x%x [+0x%x] from fw fifo fail\n",
                                ocp_src, done_size);
-                       goto exit;
+                       return ret;
                }
 
                size -= dump_size;
                done_size += dump_size;
        }
 
-       print_hex_dump(KERN_ERR, prefix_str, DUMP_PREFIX_OFFSET, 16, 1,
-                      buf, done_size, true);
-
-exit:
-       vfree(buf);
-       return ret;
+       return 0;
 }
 EXPORT_SYMBOL(rtw_dump_fw);
 
-int rtw_dump_reg(struct rtw_dev *rtwdev, const u32 addr, const u32 size,
-                const char *prefix_str)
+int rtw_dump_reg(struct rtw_dev *rtwdev, const u32 addr, const u32 size)
 {
        u8 *buf;
        u32 i;
                return -EINVAL;
        }
 
-       buf = vzalloc(size);
+       buf = rtw_fwcd_next(rtwdev, RTW_FWCD_REG, size);
        if (!buf)
                return -ENOMEM;
 
        for (i = 0; i < size; i += 4)
                *(u32 *)(buf + i) = rtw_read32(rtwdev, addr + i);
 
-       print_hex_dump(KERN_ERR, prefix_str, DUMP_PREFIX_OFFSET, 16, 4, buf,
-                      size, true);
-
-       vfree(buf);
        return 0;
 }
 EXPORT_SYMBOL(rtw_dump_reg);
 
 static void __fw_recovery_work(struct rtw_dev *rtwdev)
 {
-
-       /* rtw_fw_dump_crash_log() returns false indicates that there are
-        * still more log to dump. Driver set 0x1cf[7:0] = 0x1 to tell firmware
-        * to dump the remaining part of the log, and firmware will trigger an
-        * IMR_C2HCMD interrupt to inform driver the log is ready.
-        */
-       if (!rtw_fw_dump_crash_log(rtwdev)) {
-               rtw_write8(rtwdev, REG_HRCV_MSG, 1);
-               return;
-       }
-       rtwdev->fw.prev_dump_seq = 0;
+       int ret = 0;
 
        set_bit(RTW_FLAG_RESTARTING, rtwdev->flags);
-       rtw_chip_dump_fw_crash(rtwdev);
+
+       ret = rtw_fwcd_prep(rtwdev);
+       if (ret)
+               goto free;
+       ret = rtw_fw_dump_crash_log(rtwdev);
+       if (ret)
+               goto free;
+       ret = rtw_chip_dump_fw_crash(rtwdev);
+       if (ret)
+               goto free;
+
+       rtw_fwcd_dump(rtwdev);
+free:
+       rtw_fwcd_free(rtwdev, !!ret);
+       rtw_write8(rtwdev, REG_MCU_TST_CFG, 0);
 
        WARN(1, "firmware crash, start reset and recover\n");
 
 
 
 struct rtw_chip_ops {
        int (*mac_init)(struct rtw_dev *rtwdev);
-       void (*dump_fw_crash)(struct rtw_dev *rtwdev);
+       int (*dump_fw_crash)(struct rtw_dev *rtwdev);
        void (*shutdown)(struct rtw_dev *rtwdev);
        int (*read_efuse)(struct rtw_dev *rtwdev, u8 *map);
        void (*phy_set_param)(struct rtw_dev *rtwdev);
        RTW_FW_FIFO_MAX,
 };
 
+enum rtw_fwcd_item {
+       RTW_FWCD_TLV,
+       RTW_FWCD_REG,
+       RTW_FWCD_ROM,
+       RTW_FWCD_IMEM,
+       RTW_FWCD_DMEM,
+       RTW_FWCD_EMEM,
+};
+
 /* hardware configuration for each IC */
 struct rtw_chip_info {
        struct rtw_chip_ops *ops;
        u8 max_power_index;
 
        u16 fw_fifo_addr[RTW_FW_FIFO_MAX];
+       const struct rtw_fwcd_segs *fwcd_segs;
+
        u8 default_1ss_tx_path;
 
        bool path_div_supported;
        const struct rtw_rqpn *rqpn;
 };
 
+struct rtw_fwcd_desc {
+       u32 size;
+       u8 *next;
+       u8 *data;
+};
+
+struct rtw_fwcd_segs {
+       const u32 *segs;
+       u8 num;
+};
+
 #define FW_CD_TYPE 0xffff
 #define FW_CD_LEN 4
 #define FW_CD_VAL 0xaabbccdd
        const struct firmware *firmware;
        struct rtw_dev *rtwdev;
        struct completion completion;
+       struct rtw_fwcd_desc fwcd_desc;
        u16 version;
        u8 sub_version;
        u8 sub_index;
        u16 h2c_version;
-       u8 prev_dump_seq;
        u32 feature;
 };
 
        clear_bit(mac_id, rtwdev->mac_id_map);
 }
 
-static inline void rtw_chip_dump_fw_crash(struct rtw_dev *rtwdev)
+static inline int rtw_chip_dump_fw_crash(struct rtw_dev *rtwdev)
 {
        if (rtwdev->chip->ops->dump_fw_crash)
-               rtwdev->chip->ops->dump_fw_crash(rtwdev);
+               return rtwdev->chip->ops->dump_fw_crash(rtwdev);
+
+       return 0;
 }
 
 void rtw_get_channel_params(struct cfg80211_chan_def *chandef,
 void rtw_fw_recovery(struct rtw_dev *rtwdev);
 void rtw_core_fw_scan_notify(struct rtw_dev *rtwdev, bool start);
 int rtw_dump_fw(struct rtw_dev *rtwdev, const u32 ocp_src, u32 size,
-               const char *prefix_str);
-int rtw_dump_reg(struct rtw_dev *rtwdev, const u32 addr, const u32 size,
-                const char *prefix_str);
+               u32 fwcd_item);
+int rtw_dump_reg(struct rtw_dev *rtwdev, const u32 addr, const u32 size);
 
 #endif
 
        return 0;
 }
 
-static void rtw8822c_dump_fw_crash(struct rtw_dev *rtwdev)
+#define FWCD_SIZE_REG_8822C 0x2000
+#define FWCD_SIZE_DMEM_8822C 0x10000
+#define FWCD_SIZE_IMEM_8822C 0x10000
+#define FWCD_SIZE_EMEM_8822C 0x20000
+#define FWCD_SIZE_ROM_8822C 0x10000
+
+static const u32 __fwcd_segs_8822c[] = {
+       FWCD_SIZE_REG_8822C,
+       FWCD_SIZE_DMEM_8822C,
+       FWCD_SIZE_IMEM_8822C,
+       FWCD_SIZE_EMEM_8822C,
+       FWCD_SIZE_ROM_8822C,
+};
+
+static const struct rtw_fwcd_segs rtw8822c_fwcd_segs = {
+       .segs = __fwcd_segs_8822c,
+       .num = ARRAY_SIZE(__fwcd_segs_8822c),
+};
+
+static int rtw8822c_dump_fw_crash(struct rtw_dev *rtwdev)
 {
-       rtw_dump_reg(rtwdev, 0x0, 0x2000, "rtw8822c reg_");
-       rtw_dump_fw(rtwdev, OCPBASE_DMEM_88XX, 0x10000, "rtw8822c DMEM_");
-       rtw_dump_fw(rtwdev, OCPBASE_IMEM_88XX, 0x10000, "rtw8822c IMEM_");
-       rtw_dump_fw(rtwdev, OCPBASE_EMEM_88XX, 0x20000, "rtw8822c EMEM_");
-       rtw_dump_fw(rtwdev, OCPBASE_ROM_88XX, 0x10000, "rtw8822c ROM_");
+#define __dump_fw_8822c(_dev, _mem) \
+       rtw_dump_fw(_dev, OCPBASE_ ## _mem ## _88XX, \
+                   FWCD_SIZE_ ## _mem ## _8822C, RTW_FWCD_ ## _mem)
+       int ret;
+
+       ret = rtw_dump_reg(rtwdev, 0x0, FWCD_SIZE_REG_8822C);
+       if (ret)
+               return ret;
+       ret = __dump_fw_8822c(rtwdev, DMEM);
+       if (ret)
+               return ret;
+       ret = __dump_fw_8822c(rtwdev, IMEM);
+       if (ret)
+               return ret;
+       ret = __dump_fw_8822c(rtwdev, EMEM);
+       if (ret)
+               return ret;
+       ret = __dump_fw_8822c(rtwdev, ROM);
+       if (ret)
+               return ret;
+
+       return 0;
+
+#undef __dump_fw_8822c
 }
 
 static void rtw8822c_rstb_3wire(struct rtw_dev *rtwdev, bool enable)
        .coex_info_hw_regs = coex_info_hw_regs_8822c,
 
        .fw_fifo_addr = {0x780, 0x700, 0x780, 0x660, 0x650, 0x680},
+       .fwcd_segs = &rtw8822c_fwcd_segs,
 };
 EXPORT_SYMBOL(rtw8822c_hw_spec);