286 lines
7.2 KiB
C
286 lines
7.2 KiB
C
|
// 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);
|
||
|
}
|