1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright 2021 Collabora ltd. */ |
3 | |
4 | #include <linux/err.h> |
5 | #include <linux/device.h> |
6 | #include <linux/devcoredump.h> |
7 | #include <linux/moduleparam.h> |
8 | #include <linux/iosys-map.h> |
9 | #include <drm/panfrost_drm.h> |
10 | #include <drm/drm_device.h> |
11 | |
12 | #include "panfrost_job.h" |
13 | #include "panfrost_gem.h" |
14 | #include "panfrost_regs.h" |
15 | #include "panfrost_dump.h" |
16 | #include "panfrost_device.h" |
17 | |
18 | static bool panfrost_dump_core = true; |
19 | module_param_named(dump_core, panfrost_dump_core, bool, 0600); |
20 | |
21 | struct panfrost_dump_iterator { |
22 | void *start; |
23 | struct panfrost_dump_object_header *hdr; |
24 | void *data; |
25 | }; |
26 | |
27 | static const unsigned short panfrost_dump_registers[] = { |
28 | SHADER_READY_LO, |
29 | SHADER_READY_HI, |
30 | TILER_READY_LO, |
31 | TILER_READY_HI, |
32 | L2_READY_LO, |
33 | L2_READY_HI, |
34 | JOB_INT_MASK, |
35 | JOB_INT_STAT, |
36 | JS_HEAD_LO(0), |
37 | JS_HEAD_HI(0), |
38 | JS_TAIL_LO(0), |
39 | JS_TAIL_HI(0), |
40 | JS_AFFINITY_LO(0), |
41 | JS_AFFINITY_HI(0), |
42 | JS_CONFIG(0), |
43 | JS_STATUS(0), |
44 | JS_HEAD_NEXT_LO(0), |
45 | JS_HEAD_NEXT_HI(0), |
46 | JS_AFFINITY_NEXT_LO(0), |
47 | JS_AFFINITY_NEXT_HI(0), |
48 | JS_CONFIG_NEXT(0), |
49 | MMU_INT_MASK, |
50 | MMU_INT_STAT, |
51 | AS_TRANSTAB_LO(0), |
52 | AS_TRANSTAB_HI(0), |
53 | AS_MEMATTR_LO(0), |
54 | AS_MEMATTR_HI(0), |
55 | AS_FAULTSTATUS(0), |
56 | AS_FAULTADDRESS_LO(0), |
57 | AS_FAULTADDRESS_HI(0), |
58 | AS_STATUS(0), |
59 | }; |
60 | |
61 | static void (struct panfrost_dump_iterator *iter, |
62 | u32 type, void *data_end) |
63 | { |
64 | struct panfrost_dump_object_header *hdr = iter->hdr; |
65 | |
66 | hdr->magic = PANFROSTDUMP_MAGIC; |
67 | hdr->type = type; |
68 | hdr->file_offset = iter->data - iter->start; |
69 | hdr->file_size = data_end - iter->data; |
70 | |
71 | iter->hdr++; |
72 | iter->data += hdr->file_size; |
73 | } |
74 | |
75 | static void |
76 | panfrost_core_dump_registers(struct panfrost_dump_iterator *iter, |
77 | struct panfrost_device *pfdev, |
78 | u32 as_nr, int slot) |
79 | { |
80 | struct panfrost_dump_registers *dumpreg = iter->data; |
81 | unsigned int i; |
82 | |
83 | for (i = 0; i < ARRAY_SIZE(panfrost_dump_registers); i++, dumpreg++) { |
84 | unsigned int js_as_offset = 0; |
85 | unsigned int reg; |
86 | |
87 | if (panfrost_dump_registers[i] >= JS_BASE && |
88 | panfrost_dump_registers[i] <= JS_BASE + JS_SLOT_STRIDE) |
89 | js_as_offset = slot * JS_SLOT_STRIDE; |
90 | else if (panfrost_dump_registers[i] >= MMU_BASE && |
91 | panfrost_dump_registers[i] <= MMU_BASE + MMU_AS_STRIDE) |
92 | js_as_offset = (as_nr << MMU_AS_SHIFT); |
93 | |
94 | reg = panfrost_dump_registers[i] + js_as_offset; |
95 | |
96 | dumpreg->reg = reg; |
97 | dumpreg->value = gpu_read(pfdev, reg); |
98 | } |
99 | |
100 | panfrost_core_dump_header(iter, PANFROSTDUMP_BUF_REG, data_end: dumpreg); |
101 | } |
102 | |
103 | void panfrost_core_dump(struct panfrost_job *job) |
104 | { |
105 | struct panfrost_device *pfdev = job->pfdev; |
106 | struct panfrost_dump_iterator iter; |
107 | struct drm_gem_object *dbo; |
108 | unsigned int n_obj, n_bomap_pages; |
109 | u64 *bomap, *bomap_start; |
110 | size_t file_size; |
111 | u32 as_nr; |
112 | int slot; |
113 | int ret, i; |
114 | |
115 | as_nr = job->mmu->as; |
116 | slot = panfrost_job_get_slot(job); |
117 | |
118 | /* Only catch the first event, or when manually re-armed */ |
119 | if (!panfrost_dump_core) |
120 | return; |
121 | panfrost_dump_core = false; |
122 | |
123 | /* At least, we dump registers and end marker */ |
124 | n_obj = 2; |
125 | n_bomap_pages = 0; |
126 | file_size = ARRAY_SIZE(panfrost_dump_registers) * |
127 | sizeof(struct panfrost_dump_registers); |
128 | |
129 | /* Add in the active buffer objects */ |
130 | for (i = 0; i < job->bo_count; i++) { |
131 | /* |
132 | * Even though the CPU could be configured to use 16K or 64K pages, this |
133 | * is a very unusual situation for most kernel setups on SoCs that have |
134 | * a Panfrost device. Also many places across the driver make the somewhat |
135 | * arbitrary assumption that Panfrost's MMU page size is the same as the CPU's, |
136 | * so let's have a sanity check to ensure that's always the case |
137 | */ |
138 | dbo = job->bos[i]; |
139 | WARN_ON(!IS_ALIGNED(dbo->size, PAGE_SIZE)); |
140 | |
141 | file_size += dbo->size; |
142 | n_bomap_pages += dbo->size >> PAGE_SHIFT; |
143 | n_obj++; |
144 | } |
145 | |
146 | /* If we have any buffer objects, add a bomap object */ |
147 | if (n_bomap_pages) { |
148 | file_size += n_bomap_pages * sizeof(*bomap); |
149 | n_obj++; |
150 | } |
151 | |
152 | /* Add the size of the headers */ |
153 | file_size += sizeof(*iter.hdr) * n_obj; |
154 | |
155 | /* |
156 | * Allocate the file in vmalloc memory, it's likely to be big. |
157 | * The reason behind these GFP flags is that we don't want to trigger the |
158 | * OOM killer in the event that not enough memory could be found for our |
159 | * dump file. We also don't want the allocator to do any error reporting, |
160 | * as the right behaviour is failing gracefully if a big enough buffer |
161 | * could not be allocated. |
162 | */ |
163 | iter.start = __vmalloc(size: file_size, GFP_KERNEL | __GFP_NOWARN | |
164 | __GFP_NORETRY); |
165 | if (!iter.start) { |
166 | dev_warn(pfdev->dev, "failed to allocate devcoredump file\n" ); |
167 | return; |
168 | } |
169 | |
170 | /* Point the data member after the headers */ |
171 | iter.hdr = iter.start; |
172 | iter.data = &iter.hdr[n_obj]; |
173 | |
174 | memset(iter.hdr, 0, iter.data - iter.start); |
175 | |
176 | /* |
177 | * For now, we write the job identifier in the register dump header, |
178 | * so that we can decode the entire dump later with pandecode |
179 | */ |
180 | iter.hdr->reghdr.jc = job->jc; |
181 | iter.hdr->reghdr.major = PANFROSTDUMP_MAJOR; |
182 | iter.hdr->reghdr.minor = PANFROSTDUMP_MINOR; |
183 | iter.hdr->reghdr.gpu_id = pfdev->features.id; |
184 | iter.hdr->reghdr.nbos = job->bo_count; |
185 | |
186 | panfrost_core_dump_registers(iter: &iter, pfdev, as_nr, slot); |
187 | |
188 | /* Reserve space for the bomap */ |
189 | if (job->bo_count) { |
190 | bomap_start = bomap = iter.data; |
191 | memset(bomap, 0, sizeof(*bomap) * n_bomap_pages); |
192 | panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_BOMAP, |
193 | data_end: bomap + n_bomap_pages); |
194 | } |
195 | |
196 | for (i = 0; i < job->bo_count; i++) { |
197 | struct iosys_map map; |
198 | struct panfrost_gem_mapping *mapping; |
199 | struct panfrost_gem_object *bo; |
200 | struct sg_page_iter page_iter; |
201 | void *vaddr; |
202 | |
203 | bo = to_panfrost_bo(obj: job->bos[i]); |
204 | mapping = job->mappings[i]; |
205 | |
206 | if (!bo->base.sgt) { |
207 | dev_err(pfdev->dev, "Panfrost Dump: BO has no sgt, cannot dump\n" ); |
208 | iter.hdr->bomap.valid = 0; |
209 | goto dump_header; |
210 | } |
211 | |
212 | ret = drm_gem_vmap_unlocked(obj: &bo->base.base, map: &map); |
213 | if (ret) { |
214 | dev_err(pfdev->dev, "Panfrost Dump: couldn't map Buffer Object\n" ); |
215 | iter.hdr->bomap.valid = 0; |
216 | goto dump_header; |
217 | } |
218 | |
219 | WARN_ON(!mapping->active); |
220 | |
221 | iter.hdr->bomap.data[0] = bomap - bomap_start; |
222 | |
223 | for_each_sgtable_page(bo->base.sgt, &page_iter, 0) { |
224 | struct page *page = sg_page_iter_page(piter: &page_iter); |
225 | |
226 | if (!IS_ERR(ptr: page)) { |
227 | *bomap++ = page_to_phys(page); |
228 | } else { |
229 | dev_err(pfdev->dev, "Panfrost Dump: wrong page\n" ); |
230 | *bomap++ = 0; |
231 | } |
232 | } |
233 | |
234 | iter.hdr->bomap.iova = mapping->mmnode.start << PAGE_SHIFT; |
235 | |
236 | vaddr = map.vaddr; |
237 | memcpy(iter.data, vaddr, bo->base.base.size); |
238 | |
239 | drm_gem_vunmap_unlocked(obj: &bo->base.base, map: &map); |
240 | |
241 | iter.hdr->bomap.valid = 1; |
242 | |
243 | : panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_BO, data_end: iter.data + |
244 | bo->base.base.size); |
245 | } |
246 | panfrost_core_dump_header(iter: &iter, PANFROSTDUMP_BUF_TRAILER, data_end: iter.data); |
247 | |
248 | dev_coredumpv(dev: pfdev->dev, data: iter.start, datalen: iter.data - iter.start, GFP_KERNEL); |
249 | } |
250 | |