// SPDX-License-Identifier: GPL-2.0 // // mcp251xfd - Microchip MCP251xFD Family CAN controller driver // // Copyright (c) 2020, 2021 Pengutronix, // Marc Kleine-Budde // Copyright (C) 2015-2018 Etnaviv Project // #include #include "mcp251xfd.h" #include "mcp251xfd-dump.h" struct mcp251xfd_dump_iter { void *start; struct mcp251xfd_dump_object_header *hdr; void *data; }; struct mcp251xfd_dump_reg_space { u16 base; u16 size; }; struct mcp251xfd_dump_ring { enum mcp251xfd_dump_object_ring_key key; u32 val; }; static const struct mcp251xfd_dump_reg_space mcp251xfd_dump_reg_space[] = { { .base = MCP251XFD_REG_CON, .size = MCP251XFD_REG_FLTOBJ(32) - MCP251XFD_REG_CON, }, { .base = MCP251XFD_RAM_START, .size = MCP251XFD_RAM_SIZE, }, { .base = MCP251XFD_REG_OSC, .size = MCP251XFD_REG_DEVID - MCP251XFD_REG_OSC, }, }; static void mcp251xfd_dump_header(struct mcp251xfd_dump_iter *iter, enum mcp251xfd_dump_object_type object_type, const void *data_end) { struct mcp251xfd_dump_object_header *hdr = iter->hdr; unsigned int len; len = data_end - iter->data; if (!len) return; hdr->magic = cpu_to_le32(MCP251XFD_DUMP_MAGIC); hdr->type = cpu_to_le32(object_type); hdr->offset = cpu_to_le32(iter->data - iter->start); hdr->len = cpu_to_le32(len); iter->hdr++; iter->data += len; } static void mcp251xfd_dump_registers(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter) { const int val_bytes = regmap_get_val_bytes(priv->map_rx); struct mcp251xfd_dump_object_reg *reg = iter->data; unsigned int i, j; int err; for (i = 0; i < ARRAY_SIZE(mcp251xfd_dump_reg_space); i++) { const struct mcp251xfd_dump_reg_space *reg_space; void *buf; reg_space = &mcp251xfd_dump_reg_space[i]; buf = kmalloc(reg_space->size, GFP_KERNEL); if (!buf) goto out; err = regmap_bulk_read(priv->map_reg, reg_space->base, buf, reg_space->size / val_bytes); if (err) { kfree(buf); continue; } for (j = 0; j < reg_space->size; j += sizeof(u32), reg++) { reg->reg = cpu_to_le32(reg_space->base + j); reg->val = cpu_to_le32p(buf + j); } kfree(buf); } out: mcp251xfd_dump_header(iter, MCP251XFD_DUMP_OBJECT_TYPE_REG, reg); } static void mcp251xfd_dump_ring(struct mcp251xfd_dump_iter *iter, enum mcp251xfd_dump_object_type object_type, const struct mcp251xfd_dump_ring *dump_ring, unsigned int len) { struct mcp251xfd_dump_object_reg *reg = iter->data; unsigned int i; for (i = 0; i < len; i++, reg++) { reg->reg = cpu_to_le32(dump_ring[i].key); reg->val = cpu_to_le32(dump_ring[i].val); } mcp251xfd_dump_header(iter, object_type, reg); } static void mcp251xfd_dump_tef_ring(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter) { const struct mcp251xfd_tef_ring *tef = priv->tef; const struct mcp251xfd_tx_ring *tx = priv->tx; const struct mcp251xfd_dump_ring dump_ring[] = { { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD, .val = tef->head, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL, .val = tef->tail, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE, .val = 0, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR, .val = 0, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR, .val = 0, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM, .val = tx->obj_num, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE, .val = sizeof(struct mcp251xfd_hw_tef_obj), }, }; mcp251xfd_dump_ring(iter, MCP251XFD_DUMP_OBJECT_TYPE_TEF, dump_ring, ARRAY_SIZE(dump_ring)); } static void mcp251xfd_dump_rx_ring_one(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter, const struct mcp251xfd_rx_ring *rx) { const struct mcp251xfd_dump_ring dump_ring[] = { { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD, .val = rx->head, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL, .val = rx->tail, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE, .val = rx->base, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR, .val = rx->nr, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR, .val = rx->fifo_nr, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM, .val = rx->obj_num, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE, .val = rx->obj_size, }, }; mcp251xfd_dump_ring(iter, MCP251XFD_DUMP_OBJECT_TYPE_RX, dump_ring, ARRAY_SIZE(dump_ring)); } static void mcp251xfd_dump_rx_ring(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter) { struct mcp251xfd_rx_ring *rx_ring; unsigned int i; mcp251xfd_for_each_rx_ring(priv, rx_ring, i) mcp251xfd_dump_rx_ring_one(priv, iter, rx_ring); } static void mcp251xfd_dump_tx_ring(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter) { const struct mcp251xfd_tx_ring *tx = priv->tx; const struct mcp251xfd_dump_ring dump_ring[] = { { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD, .val = tx->head, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL, .val = tx->tail, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE, .val = tx->base, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR, .val = tx->nr, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR, .val = tx->fifo_nr, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM, .val = tx->obj_num, }, { .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE, .val = tx->obj_size, }, }; mcp251xfd_dump_ring(iter, MCP251XFD_DUMP_OBJECT_TYPE_TX, dump_ring, ARRAY_SIZE(dump_ring)); } static void mcp251xfd_dump_end(const struct mcp251xfd_priv *priv, struct mcp251xfd_dump_iter *iter) { struct mcp251xfd_dump_object_header *hdr = iter->hdr; hdr->magic = cpu_to_le32(MCP251XFD_DUMP_MAGIC); hdr->type = cpu_to_le32(MCP251XFD_DUMP_OBJECT_TYPE_END); hdr->offset = cpu_to_le32(0); hdr->len = cpu_to_le32(0); /* provoke NULL pointer access, if used after END object */ iter->hdr = NULL; } void mcp251xfd_dump(const struct mcp251xfd_priv *priv) { struct mcp251xfd_dump_iter iter; unsigned int rings_num, obj_num; unsigned int file_size = 0; unsigned int i; /* register space + end marker */ obj_num = 2; /* register space */ for (i = 0; i < ARRAY_SIZE(mcp251xfd_dump_reg_space); i++) file_size += mcp251xfd_dump_reg_space[i].size / sizeof(u32) * sizeof(struct mcp251xfd_dump_object_reg); /* TEF ring, RX rings, TX ring */ rings_num = 1 + priv->rx_ring_num + 1; obj_num += rings_num; file_size += rings_num * __MCP251XFD_DUMP_OBJECT_RING_KEY_MAX * sizeof(struct mcp251xfd_dump_object_reg); /* size of the headers */ file_size += sizeof(*iter.hdr) * obj_num; /* allocate the file in vmalloc memory, it's likely to be big */ iter.start = __vmalloc(file_size, GFP_KERNEL | __GFP_NOWARN | __GFP_ZERO | __GFP_NORETRY); if (!iter.start) { netdev_warn(priv->ndev, "Failed to allocate devcoredump file.\n"); return; } /* point the data member after the headers */ iter.hdr = iter.start; iter.data = &iter.hdr[obj_num]; mcp251xfd_dump_registers(priv, &iter); mcp251xfd_dump_tef_ring(priv, &iter); mcp251xfd_dump_rx_ring(priv, &iter); mcp251xfd_dump_tx_ring(priv, &iter); mcp251xfd_dump_end(priv, &iter); dev_coredumpv(&priv->spi->dev, iter.start, iter.data - iter.start, GFP_KERNEL); }