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 | |
15 | struct mcp251xfd_dump_iter { |
16 | void *start; |
17 | struct mcp251xfd_dump_object_header *hdr; |
18 | void *data; |
19 | }; |
20 | |
21 | struct mcp251xfd_dump_reg_space { |
22 | u16 base; |
23 | u16 size; |
24 | }; |
25 | |
26 | struct mcp251xfd_dump_ring { |
27 | enum mcp251xfd_dump_object_ring_key key; |
28 | u32 val; |
29 | }; |
30 | |
31 | static 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 | |
44 | static void (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 | |
64 | static 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 | |
101 | static 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 | |
117 | static 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 | |
151 | static 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 | |
184 | static 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 | |
194 | static 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 | |
227 | static 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 | |
241 | void 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 | |