This patch removes the hard coded assumption that the TX ring uses hardware FIFO 1. This allows the hardware FIFO 1 to be used for RX and the next free FIFO for TX. This gives the opportunity to minimize the number of SPI transfers in the IRQ handler. The read of the IRQ status register and RX FIFO status registers can be combined into single SPI transfer. If the RX ring uses FIFO 1, the overall length of the transfer is smaller than in the original layout, where the RX FIFO comes after the TX FIFO. Link: https://lore.kernel.org/all/20220217103826.2299157-2-mkl@pengutronix.de Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
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 ring, TX rings */
|
|
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);
|
|
}
|