@@ -9,6 +9,11 @@
#include "t7xx_port_ap_msg.h"
#include "t7xx_port_devlink.h"
+static struct t7xx_devlink_region_info t7xx_devlink_region_infos[] = {
+ [T7XX_MRDUMP_INDEX] = {"mr_dump", T7XX_MRDUMP_SIZE},
+ [T7XX_LKDUMP_INDEX] = {"lk_dump", T7XX_LKDUMP_SIZE},
+};
+
static int t7xx_devlink_port_read(struct t7xx_port *port, char *buf, size_t count)
{
struct sk_buff *skb;
@@ -177,6 +182,149 @@ static int t7xx_devlink_fb_flash_partition(struct t7xx_port *port, const char *p
return t7xx_devlink_fb_flash(port, partition);
}
+static int t7xx_devlink_fb_cmd_send(struct t7xx_port *port, char *cmd)
+{
+ int len = strlen(cmd);
+ int ret;
+
+ ret = t7xx_devlink_port_write(port, cmd, len);
+ if (ret == len)
+ return 0;
+
+ return ret;
+}
+
+static int t7xx_devlink_fb_get_core(struct t7xx_port *port)
+{
+ u32 mrd_mb = T7XX_MRDUMP_SIZE / (1024 * 1024);
+ struct t7xx_devlink *dl = port->t7xx_dev->dl;
+ char mcmd[T7XX_FB_MCMD_SIZE + 1];
+ size_t offset_dlen = 0;
+ int clen, dlen, ret;
+
+ dl->regions[T7XX_MRDUMP_INDEX].buf = vmalloc(dl->regions[T7XX_MRDUMP_INDEX].info->size);
+ if (!dl->regions[T7XX_MRDUMP_INDEX].buf)
+ return -ENOMEM;
+
+ set_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ ret = t7xx_devlink_fb_raw_command(T7XX_FB_CMD_OEM_MRDUMP, port, NULL);
+ if (ret) {
+ dev_err(port->dev, "%s command failed\n", T7XX_FB_CMD_OEM_MRDUMP);
+ goto free_mem;
+ }
+
+ while (dl->regions[T7XX_MRDUMP_INDEX].info->size > offset_dlen) {
+ clen = t7xx_devlink_port_read(port, mcmd, sizeof(mcmd) - 1);
+ if (clen <= 0)
+ goto free_mem;
+
+ mcmd[clen] = '\0';
+ if (!strcmp(mcmd, T7XX_FB_CMD_RTS)) {
+ memset(mcmd, 0, sizeof(mcmd));
+ ret = t7xx_devlink_fb_cmd_send(port, T7XX_FB_CMD_CTS);
+ if (ret < 0) {
+ dev_err(port->dev, "write for _CTS failed:%zu\n",
+ strlen(T7XX_FB_CMD_CTS));
+ goto free_mem;
+ }
+
+ dlen = t7xx_devlink_port_read(port, dl->regions[T7XX_MRDUMP_INDEX].buf +
+ offset_dlen, T7XX_FB_MDATA_SIZE);
+ if (dlen <= 0) {
+ dev_err(port->dev, "read data error(%d)\n", dlen);
+ ret = dlen;
+ goto free_mem;
+ }
+ offset_dlen += dlen;
+
+ ret = t7xx_devlink_fb_cmd_send(port, T7XX_FB_CMD_FIN);
+ if (ret < 0) {
+ dev_err(port->dev, "_FIN failed, (Read %05zu:%05zu)\n",
+ strlen(T7XX_FB_CMD_FIN), offset_dlen);
+ goto free_mem;
+ }
+ continue;
+ } else if (!strcmp(mcmd, T7XX_FB_RESP_MRDUMP_DONE)) {
+ dev_dbg(port->dev, "%s! size:%zd\n", T7XX_FB_RESP_MRDUMP_DONE, offset_dlen);
+ clear_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ return 0;
+ }
+ dev_err(port->dev, "getcore protocol error (read len %05d, response %s)\n",
+ clen, mcmd);
+ ret = -EPROTO;
+ goto free_mem;
+ }
+
+ dev_err(port->dev, "mrdump exceeds %uMB size. Discarded!\n", mrd_mb);
+
+free_mem:
+ vfree(dl->regions[T7XX_MRDUMP_INDEX].buf);
+ clear_bit(T7XX_MRDUMP_STATUS, &dl->status);
+ return ret;
+}
+
+static int t7xx_devlink_fb_dump_log(struct t7xx_port *port)
+{
+ struct t7xx_devlink *dl = port->t7xx_dev->dl;
+ struct t7xx_devlink_region *lkdump_region;
+ char rsp[T7XX_FB_RESPONSE_SIZE];
+ int datasize = 0, ret;
+ size_t offset = 0;
+
+ if (dl->status != T7XX_DEVLINK_IDLE) {
+ dev_err(&dl->t7xx_dev->pdev->dev, "Modem is busy!\n");
+ return -EBUSY;
+ }
+
+ set_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ ret = t7xx_devlink_fb_raw_command(T7XX_FB_CMD_OEM_LKDUMP, port, rsp);
+ if (ret) {
+ dev_err(port->dev, "%s command returns failure\n", T7XX_FB_CMD_OEM_LKDUMP);
+ goto err_clear_bit;
+ }
+
+ ret = kstrtoint(rsp, 16, &datasize);
+ if (ret) {
+ dev_err(port->dev, "bad value\n");
+ goto err_clear_bit;
+ }
+
+ lkdump_region = &dl->regions[T7XX_LKDUMP_INDEX];
+ if (datasize > lkdump_region->info->size) {
+ dev_err(port->dev, "lkdump size is more than %dKB. Discarded!\n",
+ T7XX_LKDUMP_SIZE / 1024);
+ ret = -EFBIG;
+ goto err_clear_bit;
+ }
+
+ lkdump_region->buf = vmalloc(lkdump_region->info->size);
+ if (!lkdump_region->buf) {
+ ret = -ENOMEM;
+ goto err_clear_bit;
+ }
+
+ while (datasize > 0) {
+ int dlen = t7xx_devlink_port_read(port, lkdump_region->buf + offset, datasize);
+
+ if (dlen <= 0) {
+ dev_err(port->dev, "lkdump read error ret = %d\n", dlen);
+ ret = dlen;
+ goto err_clear_bit;
+ }
+
+ datasize -= dlen;
+ offset += dlen;
+ }
+
+ dev_dbg(port->dev, "LKDUMP DONE! size:%zd\n", offset);
+ clear_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ return t7xx_devlink_fb_handle_response(port, NULL);
+
+err_clear_bit:
+ clear_bit(T7XX_LKDUMP_STATUS, &dl->status);
+ return ret;
+}
+
static int t7xx_devlink_flash_update(struct devlink *devlink,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
@@ -387,6 +535,65 @@ static const struct devlink_ops devlink_flash_ops = {
.reload_up = t7xx_devlink_reload_up,
};
+static int t7xx_devlink_region_snapshot(struct devlink *dl, const struct devlink_region_ops *ops,
+ struct netlink_ext_ack *extack, u8 **data)
+{
+ struct t7xx_devlink *t7xx_dl = devlink_priv(dl);
+ struct t7xx_devlink_region *region = ops->priv;
+ struct t7xx_port *port = t7xx_dl->port;
+ u8 *snapshot_mem;
+
+ if (t7xx_dl->status != T7XX_DEVLINK_IDLE)
+ return -EBUSY;
+
+ if (!strncmp(ops->name, "mr_dump", strlen("mr_dump"))) {
+ snapshot_mem = vmalloc(region->info->size);
+ memcpy(snapshot_mem, region->buf, region->info->size);
+ *data = snapshot_mem;
+ } else if (!strncmp(ops->name, "lk_dump", strlen("lk_dump"))) {
+ int ret;
+
+ ret = t7xx_devlink_fb_dump_log(port);
+ if (ret)
+ return ret;
+
+ *data = region->buf;
+ }
+
+ return 0;
+}
+
+static_assert(ARRAY_SIZE(t7xx_devlink_region_infos) ==
+ ARRAY_SIZE(((struct t7xx_devlink *)NULL)->regions));
+
+/* To create regions for dump files */
+static int t7xx_devlink_create_regions(struct t7xx_devlink *dl)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(t7xx_devlink_region_infos); i++) {
+ dl->regions[i].info = &t7xx_devlink_region_infos[i];
+ dl->regions[i].ops.name = dl->regions[i].info->name;
+ dl->regions[i].ops.snapshot = t7xx_devlink_region_snapshot;
+ dl->regions[i].ops.destructor = vfree;
+ dl->regions[i].dlreg = devlink_region_create(dl->ctx, &dl->regions[i].ops,
+ T7XX_MAX_SNAPSHOTS,
+ t7xx_devlink_region_infos[i].size);
+ if (IS_ERR(dl->regions[i].dlreg)) {
+ ret = PTR_ERR(dl->regions[i].dlreg);
+ dev_err(dl->port->dev, "create devlink region failed, err %d\n", ret);
+ while (i >= 0)
+ devlink_region_destroy(dl->regions[i--].dlreg);
+
+ return ret;
+ }
+
+ dl->regions[i].ops.priv = &dl->regions[i];
+ }
+
+ return 0;
+}
+
int t7xx_devlink_register(struct t7xx_pci_dev *t7xx_dev)
{
union devlink_param_value value;
@@ -422,6 +629,14 @@ void t7xx_devlink_unregister(struct t7xx_pci_dev *t7xx_dev)
devlink_free(dl_ctx);
}
+static void t7xx_devlink_work(struct work_struct *work)
+{
+ struct t7xx_devlink *dl;
+
+ dl = container_of(work, struct t7xx_devlink, ws);
+ t7xx_devlink_fb_get_core(dl->port);
+}
+
/**
* t7xx_devlink_init - Initialize devlink to t7xx driver
* @port: Pointer to port structure
@@ -431,28 +646,56 @@ void t7xx_devlink_unregister(struct t7xx_pci_dev *t7xx_dev)
static int t7xx_devlink_init(struct t7xx_port *port)
{
struct t7xx_devlink *dl = port->t7xx_dev->dl;
+ struct workqueue_struct *dl_wq;
+ int rc;
+
+ dl_wq = create_workqueue("t7xx_devlink");
+ if (!dl_wq) {
+ dev_err(port->dev, "create_workqueue failed\n");
+ return -ENODATA;
+ }
+ INIT_WORK(&dl->ws, t7xx_devlink_work);
port->rx_length_th = T7XX_MAX_QUEUE_LENGTH;
dl->mode = T7XX_NORMAL_MODE;
dl->status = T7XX_DEVLINK_IDLE;
+ dl->wq = dl_wq;
dl->port = port;
+ rc = t7xx_devlink_create_regions(dl);
+ if (rc) {
+ destroy_workqueue(dl->wq);
+ dev_err(port->dev, "devlink region creation failed, rc %d\n", rc);
+ return -ENOMEM;
+ }
+
return 0;
}
static void t7xx_devlink_uninit(struct t7xx_port *port)
{
struct t7xx_devlink *dl = port->t7xx_dev->dl;
+ int i;
+
+ vfree(dl->regions[T7XX_MRDUMP_INDEX].buf);
dl->mode = T7XX_NORMAL_MODE;
+ destroy_workqueue(dl->wq);
+
+ for (i = 0; i < ARRAY_SIZE(t7xx_devlink_region_infos); ++i)
+ devlink_region_destroy(dl->regions[i].dlreg);
skb_queue_purge(&port->rx_skb_list);
}
static int t7xx_devlink_enable_chl(struct t7xx_port *port)
{
+ struct t7xx_devlink *dl = port->t7xx_dev->dl;
+
t7xx_port_enable_chl(port);
+ if (dl->mode == T7XX_FB_DUMP_MODE)
+ queue_work(dl->wq, &dl->ws);
return 0;
}
@@ -12,27 +12,66 @@
#define T7XX_MAX_QUEUE_LENGTH 32
#define T7XX_FB_COMMAND_SIZE 64
#define T7XX_FB_RESPONSE_SIZE 512
+#define T7XX_FB_MCMD_SIZE 64
+#define T7XX_FB_MDATA_SIZE 1024
#define T7XX_FB_RESP_COUNT 30
+#define T7XX_FB_EVENT_SIZE 50
+
+#define T7XX_MAX_SNAPSHOTS 1
+#define T7XX_MRDUMP_SIZE (160 * 1024 * 1024)
+#define T7XX_LKDUMP_SIZE (256 * 1024)
+#define T7XX_TOTAL_REGIONS 2
+
#define T7XX_FLASH_STATUS 0
+#define T7XX_MRDUMP_STATUS 1
+#define T7XX_LKDUMP_STATUS 2
#define T7XX_GET_INFO 3
-
#define T7XX_DEVLINK_IDLE 0
+
#define T7XX_NORMAL_MODE 0
#define T7XX_FB_DL_MODE 1
+#define T7XX_FB_DUMP_MODE 2
+#define T7XX_FB_CMD_RTS "_RTS"
+#define T7XX_FB_CMD_CTS "_CTS"
+#define T7XX_FB_CMD_FIN "_FIN"
+#define T7XX_FB_CMD_OEM_MRDUMP "oem mrdump"
+#define T7XX_FB_CMD_OEM_LKDUMP "oem dump_pllk_log"
#define T7XX_FB_CMD_DOWNLOAD "download"
#define T7XX_FB_CMD_FLASH "flash"
#define T7XX_FB_CMD_REBOOT "reboot"
+#define T7XX_FB_RESP_MRDUMP_DONE "MRDUMP08_DONE"
#define T7XX_FB_RESP_OKAY "OKAY"
#define T7XX_FB_RESP_FAIL "FAIL"
#define T7XX_FB_RESP_DATA "DATA"
#define T7XX_FB_RESP_INFO "INFO"
#define T7XX_FB_CMD_GET_VER "get_version"
+/* Internal region indexes */
+enum t7xx_regions {
+ T7XX_MRDUMP_INDEX,
+ T7XX_LKDUMP_INDEX,
+};
+
+struct t7xx_devlink_region_info {
+ const char *name;
+ size_t size;
+};
+
+struct t7xx_devlink_region {
+ struct t7xx_devlink_region_info *info;
+ struct devlink_region_ops ops;
+ struct devlink_region *dlreg;
+ void *buf;
+};
+
struct t7xx_devlink {
+ struct t7xx_devlink_region regions[T7XX_TOTAL_REGIONS];
struct t7xx_pci_dev *t7xx_dev;
+ struct workqueue_struct *wq;
struct t7xx_port *port;
+ struct work_struct ws;
struct devlink *ctx;
unsigned long status;
u8 mode;
@@ -244,6 +244,8 @@ static void t7xx_lk_stage_event_handling(struct t7xx_fsm_ctl *ctl, unsigned int
if (lk_event == LK_EVENT_CREATE_POST_DL_PORT)
md->t7xx_dev->dl->mode = T7XX_FB_DL_MODE;
+ else
+ md->t7xx_dev->dl->mode = T7XX_FB_DUMP_MODE;
port->port_conf->ops->enable_chl(port);
t7xx_cldma_start(md_ctrl);