1// SPDX-License-Identifier: GPL-2.0
2//
3// mcp251xfd - Microchip MCP251xFD Family CAN controller driver
4//
5// Copyright (c) 2020, 2021 Pengutronix,
6// Marc Kleine-Budde <kernel@pengutronix.de>
7// Copyright (C) 2015-2018 Etnaviv Project
8//
9
10#include <linux/devcoredump.h>
11
12#include "mcp251xfd.h"
13#include "mcp251xfd-dump.h"
14
15struct mcp251xfd_dump_iter {
16 void *start;
17 struct mcp251xfd_dump_object_header *hdr;
18 void *data;
19};
20
21struct mcp251xfd_dump_reg_space {
22 u16 base;
23 u16 size;
24};
25
26struct mcp251xfd_dump_ring {
27 enum mcp251xfd_dump_object_ring_key key;
28 u32 val;
29};
30
31static const struct mcp251xfd_dump_reg_space mcp251xfd_dump_reg_space[] = {
32 {
33 .base = MCP251XFD_REG_CON,
34 .size = MCP251XFD_REG_FLTOBJ(32) - MCP251XFD_REG_CON,
35 }, {
36 .base = MCP251XFD_RAM_START,
37 .size = MCP251XFD_RAM_SIZE,
38 }, {
39 .base = MCP251XFD_REG_OSC,
40 .size = MCP251XFD_REG_DEVID - MCP251XFD_REG_OSC,
41 },
42};
43
44static void mcp251xfd_dump_header(struct mcp251xfd_dump_iter *iter,
45 enum mcp251xfd_dump_object_type object_type,
46 const void *data_end)
47{
48 struct mcp251xfd_dump_object_header *hdr = iter->hdr;
49 unsigned int len;
50
51 len = data_end - iter->data;
52 if (!len)
53 return;
54
55 hdr->magic = cpu_to_le32(MCP251XFD_DUMP_MAGIC);
56 hdr->type = cpu_to_le32(object_type);
57 hdr->offset = cpu_to_le32(iter->data - iter->start);
58 hdr->len = cpu_to_le32(len);
59
60 iter->hdr++;
61 iter->data += len;
62}
63
64static void mcp251xfd_dump_registers(const struct mcp251xfd_priv *priv,
65 struct mcp251xfd_dump_iter *iter)
66{
67 const int val_bytes = regmap_get_val_bytes(map: priv->map_rx);
68 struct mcp251xfd_dump_object_reg *reg = iter->data;
69 unsigned int i, j;
70 int err;
71
72 for (i = 0; i < ARRAY_SIZE(mcp251xfd_dump_reg_space); i++) {
73 const struct mcp251xfd_dump_reg_space *reg_space;
74 void *buf;
75
76 reg_space = &mcp251xfd_dump_reg_space[i];
77
78 buf = kmalloc(size: reg_space->size, GFP_KERNEL);
79 if (!buf)
80 goto out;
81
82 err = regmap_bulk_read(map: priv->map_reg, reg: reg_space->base,
83 val: buf, val_count: reg_space->size / val_bytes);
84 if (err) {
85 kfree(objp: buf);
86 continue;
87 }
88
89 for (j = 0; j < reg_space->size; j += sizeof(u32), reg++) {
90 reg->reg = cpu_to_le32(reg_space->base + j);
91 reg->val = cpu_to_le32p(p: buf + j);
92 }
93
94 kfree(objp: buf);
95 }
96
97 out:
98 mcp251xfd_dump_header(iter, object_type: MCP251XFD_DUMP_OBJECT_TYPE_REG, data_end: reg);
99}
100
101static void mcp251xfd_dump_ring(struct mcp251xfd_dump_iter *iter,
102 enum mcp251xfd_dump_object_type object_type,
103 const struct mcp251xfd_dump_ring *dump_ring,
104 unsigned int len)
105{
106 struct mcp251xfd_dump_object_reg *reg = iter->data;
107 unsigned int i;
108
109 for (i = 0; i < len; i++, reg++) {
110 reg->reg = cpu_to_le32(dump_ring[i].key);
111 reg->val = cpu_to_le32(dump_ring[i].val);
112 }
113
114 mcp251xfd_dump_header(iter, object_type, data_end: reg);
115}
116
117static void mcp251xfd_dump_tef_ring(const struct mcp251xfd_priv *priv,
118 struct mcp251xfd_dump_iter *iter)
119{
120 const struct mcp251xfd_tef_ring *tef = priv->tef;
121 const struct mcp251xfd_tx_ring *tx = priv->tx;
122 const struct mcp251xfd_dump_ring dump_ring[] = {
123 {
124 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD,
125 .val = tef->head,
126 }, {
127 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL,
128 .val = tef->tail,
129 }, {
130 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE,
131 .val = 0,
132 }, {
133 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR,
134 .val = 0,
135 }, {
136 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR,
137 .val = 0,
138 }, {
139 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM,
140 .val = tx->obj_num,
141 }, {
142 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE,
143 .val = sizeof(struct mcp251xfd_hw_tef_obj),
144 },
145 };
146
147 mcp251xfd_dump_ring(iter, object_type: MCP251XFD_DUMP_OBJECT_TYPE_TEF,
148 dump_ring, ARRAY_SIZE(dump_ring));
149}
150
151static void mcp251xfd_dump_rx_ring_one(const struct mcp251xfd_priv *priv,
152 struct mcp251xfd_dump_iter *iter,
153 const struct mcp251xfd_rx_ring *rx)
154{
155 const struct mcp251xfd_dump_ring dump_ring[] = {
156 {
157 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD,
158 .val = rx->head,
159 }, {
160 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL,
161 .val = rx->tail,
162 }, {
163 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE,
164 .val = rx->base,
165 }, {
166 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR,
167 .val = rx->nr,
168 }, {
169 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR,
170 .val = rx->fifo_nr,
171 }, {
172 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM,
173 .val = rx->obj_num,
174 }, {
175 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE,
176 .val = rx->obj_size,
177 },
178 };
179
180 mcp251xfd_dump_ring(iter, object_type: MCP251XFD_DUMP_OBJECT_TYPE_RX,
181 dump_ring, ARRAY_SIZE(dump_ring));
182}
183
184static void mcp251xfd_dump_rx_ring(const struct mcp251xfd_priv *priv,
185 struct mcp251xfd_dump_iter *iter)
186{
187 struct mcp251xfd_rx_ring *rx_ring;
188 unsigned int i;
189
190 mcp251xfd_for_each_rx_ring(priv, rx_ring, i)
191 mcp251xfd_dump_rx_ring_one(priv, iter, rx: rx_ring);
192}
193
194static void mcp251xfd_dump_tx_ring(const struct mcp251xfd_priv *priv,
195 struct mcp251xfd_dump_iter *iter)
196{
197 const struct mcp251xfd_tx_ring *tx = priv->tx;
198 const struct mcp251xfd_dump_ring dump_ring[] = {
199 {
200 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_HEAD,
201 .val = tx->head,
202 }, {
203 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_TAIL,
204 .val = tx->tail,
205 }, {
206 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_BASE,
207 .val = tx->base,
208 }, {
209 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_NR,
210 .val = tx->nr,
211 }, {
212 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_FIFO_NR,
213 .val = tx->fifo_nr,
214 }, {
215 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_NUM,
216 .val = tx->obj_num,
217 }, {
218 .key = MCP251XFD_DUMP_OBJECT_RING_KEY_OBJ_SIZE,
219 .val = tx->obj_size,
220 },
221 };
222
223 mcp251xfd_dump_ring(iter, object_type: MCP251XFD_DUMP_OBJECT_TYPE_TX,
224 dump_ring, ARRAY_SIZE(dump_ring));
225}
226
227static void mcp251xfd_dump_end(const struct mcp251xfd_priv *priv,
228 struct mcp251xfd_dump_iter *iter)
229{
230 struct mcp251xfd_dump_object_header *hdr = iter->hdr;
231
232 hdr->magic = cpu_to_le32(MCP251XFD_DUMP_MAGIC);
233 hdr->type = cpu_to_le32(MCP251XFD_DUMP_OBJECT_TYPE_END);
234 hdr->offset = cpu_to_le32(0);
235 hdr->len = cpu_to_le32(0);
236
237 /* provoke NULL pointer access, if used after END object */
238 iter->hdr = NULL;
239}
240
241void mcp251xfd_dump(const struct mcp251xfd_priv *priv)
242{
243 struct mcp251xfd_dump_iter iter;
244 unsigned int rings_num, obj_num;
245 unsigned int file_size = 0;
246 unsigned int i;
247
248 /* register space + end marker */
249 obj_num = 2;
250
251 /* register space */
252 for (i = 0; i < ARRAY_SIZE(mcp251xfd_dump_reg_space); i++)
253 file_size += mcp251xfd_dump_reg_space[i].size / sizeof(u32) *
254 sizeof(struct mcp251xfd_dump_object_reg);
255
256 /* TEF ring, RX rings, TX ring */
257 rings_num = 1 + priv->rx_ring_num + 1;
258 obj_num += rings_num;
259 file_size += rings_num * __MCP251XFD_DUMP_OBJECT_RING_KEY_MAX *
260 sizeof(struct mcp251xfd_dump_object_reg);
261
262 /* size of the headers */
263 file_size += sizeof(*iter.hdr) * obj_num;
264
265 /* allocate the file in vmalloc memory, it's likely to be big */
266 iter.start = __vmalloc(size: file_size, GFP_KERNEL | __GFP_NOWARN |
267 __GFP_ZERO | __GFP_NORETRY);
268 if (!iter.start) {
269 netdev_warn(dev: priv->ndev, format: "Failed to allocate devcoredump file.\n");
270 return;
271 }
272
273 /* point the data member after the headers */
274 iter.hdr = iter.start;
275 iter.data = &iter.hdr[obj_num];
276
277 mcp251xfd_dump_registers(priv, iter: &iter);
278 mcp251xfd_dump_tef_ring(priv, iter: &iter);
279 mcp251xfd_dump_rx_ring(priv, iter: &iter);
280 mcp251xfd_dump_tx_ring(priv, iter: &iter);
281 mcp251xfd_dump_end(priv, iter: &iter);
282
283 dev_coredumpv(dev: &priv->spi->dev, data: iter.start,
284 datalen: iter.data - iter.start, GFP_KERNEL);
285}
286

source code of linux/drivers/net/can/spi/mcp251xfd/mcp251xfd-dump.c