| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // mcp251xfd - Microchip MCP251xFD Family CAN controller driver |
| // |
| // Copyright (c) 2020, 2021 Pengutronix, |
| // Marc Kleine-Budde <kernel@pengutronix.de> |
| // Copyright (C) 2015-2018 Etnaviv Project |
| // |
| |
| #include <linux/devcoredump.h> |
| |
| #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); |
| } |