1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Qualcomm Peripheral Image Loader helpers |
4 | * |
5 | * Copyright (C) 2016 Linaro Ltd |
6 | * Copyright (C) 2015 Sony Mobile Communications Inc |
7 | * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. |
8 | */ |
9 | |
10 | #include <linux/firmware.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/notifier.h> |
14 | #include <linux/remoteproc.h> |
15 | #include <linux/remoteproc/qcom_rproc.h> |
16 | #include <linux/rpmsg/qcom_glink.h> |
17 | #include <linux/rpmsg/qcom_smd.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/soc/qcom/mdt_loader.h> |
20 | #include <linux/soc/qcom/smem.h> |
21 | |
22 | #include "remoteproc_internal.h" |
23 | #include "qcom_common.h" |
24 | |
25 | #define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev) |
26 | #define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev) |
27 | #define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev) |
28 | |
29 | #define MAX_NUM_OF_SS 10 |
30 | #define MAX_REGION_NAME_LENGTH 16 |
31 | #define SBL_MINIDUMP_SMEM_ID 602 |
32 | #define MINIDUMP_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0) |
33 | #define MINIDUMP_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0) |
34 | #define MINIDUMP_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0) |
35 | |
36 | /** |
37 | * struct minidump_region - Minidump region |
38 | * @name : Name of the region to be dumped |
39 | * @seq_num: : Use to differentiate regions with same name. |
40 | * @valid : This entry to be dumped (if set to 1) |
41 | * @address : Physical address of region to be dumped |
42 | * @size : Size of the region |
43 | */ |
44 | struct minidump_region { |
45 | char name[MAX_REGION_NAME_LENGTH]; |
46 | __le32 seq_num; |
47 | __le32 valid; |
48 | __le64 address; |
49 | __le64 size; |
50 | }; |
51 | |
52 | /** |
53 | * struct minidump_subsystem - Subsystem's SMEM Table of content |
54 | * @status : Subsystem toc init status |
55 | * @enabled : if set to 1, this region would be copied during coredump |
56 | * @encryption_status: Encryption status for this subsystem |
57 | * @encryption_required : Decides to encrypt the subsystem regions or not |
58 | * @region_count : Number of regions added in this subsystem toc |
59 | * @regions_baseptr : regions base pointer of the subsystem |
60 | */ |
61 | struct minidump_subsystem { |
62 | __le32 status; |
63 | __le32 enabled; |
64 | __le32 encryption_status; |
65 | __le32 encryption_required; |
66 | __le32 region_count; |
67 | __le64 regions_baseptr; |
68 | }; |
69 | |
70 | /** |
71 | * struct minidump_global_toc - Global Table of Content |
72 | * @status : Global Minidump init status |
73 | * @md_revision : Minidump revision |
74 | * @enabled : Minidump enable status |
75 | * @subsystems : Array of subsystems toc |
76 | */ |
77 | struct minidump_global_toc { |
78 | __le32 status; |
79 | __le32 md_revision; |
80 | __le32 enabled; |
81 | struct minidump_subsystem subsystems[MAX_NUM_OF_SS]; |
82 | }; |
83 | |
84 | struct qcom_ssr_subsystem { |
85 | const char *name; |
86 | struct srcu_notifier_head notifier_list; |
87 | struct list_head list; |
88 | }; |
89 | |
90 | static LIST_HEAD(qcom_ssr_subsystem_list); |
91 | static DEFINE_MUTEX(qcom_ssr_subsys_lock); |
92 | |
93 | static void qcom_minidump_cleanup(struct rproc *rproc) |
94 | { |
95 | struct rproc_dump_segment *entry, *tmp; |
96 | |
97 | list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) { |
98 | list_del(entry: &entry->node); |
99 | kfree(objp: entry->priv); |
100 | kfree(objp: entry); |
101 | } |
102 | } |
103 | |
104 | static int qcom_add_minidump_segments(struct rproc *rproc, struct minidump_subsystem *subsystem, |
105 | void (*rproc_dumpfn_t)(struct rproc *rproc, struct rproc_dump_segment *segment, |
106 | void *dest, size_t offset, size_t size)) |
107 | { |
108 | struct minidump_region __iomem *ptr; |
109 | struct minidump_region region; |
110 | int seg_cnt, i; |
111 | dma_addr_t da; |
112 | size_t size; |
113 | char *name; |
114 | |
115 | if (WARN_ON(!list_empty(&rproc->dump_segments))) { |
116 | dev_err(&rproc->dev, "dump segment list already populated\n" ); |
117 | return -EUCLEAN; |
118 | } |
119 | |
120 | seg_cnt = le32_to_cpu(subsystem->region_count); |
121 | ptr = ioremap(offset: (unsigned long)le64_to_cpu(subsystem->regions_baseptr), |
122 | size: seg_cnt * sizeof(struct minidump_region)); |
123 | if (!ptr) |
124 | return -EFAULT; |
125 | |
126 | for (i = 0; i < seg_cnt; i++) { |
127 | memcpy_fromio(®ion, ptr + i, sizeof(region)); |
128 | if (le32_to_cpu(region.valid) == MINIDUMP_REGION_VALID) { |
129 | name = kstrndup(s: region.name, MAX_REGION_NAME_LENGTH - 1, GFP_KERNEL); |
130 | if (!name) { |
131 | iounmap(addr: ptr); |
132 | return -ENOMEM; |
133 | } |
134 | da = le64_to_cpu(region.address); |
135 | size = le64_to_cpu(region.size); |
136 | rproc_coredump_add_custom_segment(rproc, da, size, dumpfn: rproc_dumpfn_t, priv: name); |
137 | } |
138 | } |
139 | |
140 | iounmap(addr: ptr); |
141 | return 0; |
142 | } |
143 | |
144 | void qcom_minidump(struct rproc *rproc, unsigned int minidump_id, |
145 | void (*rproc_dumpfn_t)(struct rproc *rproc, |
146 | struct rproc_dump_segment *segment, void *dest, size_t offset, |
147 | size_t size)) |
148 | { |
149 | int ret; |
150 | struct minidump_subsystem *subsystem; |
151 | struct minidump_global_toc *toc; |
152 | |
153 | /* Get Global minidump ToC*/ |
154 | toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, NULL); |
155 | |
156 | /* check if global table pointer exists and init is set */ |
157 | if (IS_ERR(ptr: toc) || !toc->status) { |
158 | dev_err(&rproc->dev, "Minidump TOC not found in SMEM\n" ); |
159 | return; |
160 | } |
161 | |
162 | /* Get subsystem table of contents using the minidump id */ |
163 | subsystem = &toc->subsystems[minidump_id]; |
164 | |
165 | /** |
166 | * Collect minidump if SS ToC is valid and segment table |
167 | * is initialized in memory and encryption status is set. |
168 | */ |
169 | if (subsystem->regions_baseptr == 0 || |
170 | le32_to_cpu(subsystem->status) != 1 || |
171 | le32_to_cpu(subsystem->enabled) != MINIDUMP_SS_ENABLED) { |
172 | return rproc_coredump(rproc); |
173 | } |
174 | |
175 | if (le32_to_cpu(subsystem->encryption_status) != MINIDUMP_SS_ENCR_DONE) { |
176 | dev_err(&rproc->dev, "Minidump not ready, skipping\n" ); |
177 | return; |
178 | } |
179 | |
180 | /** |
181 | * Clear out the dump segments populated by parse_fw before |
182 | * re-populating them with minidump segments. |
183 | */ |
184 | rproc_coredump_cleanup(rproc); |
185 | |
186 | ret = qcom_add_minidump_segments(rproc, subsystem, rproc_dumpfn_t); |
187 | if (ret) { |
188 | dev_err(&rproc->dev, "Failed with error: %d while adding minidump entries\n" , ret); |
189 | goto clean_minidump; |
190 | } |
191 | rproc_coredump_using_sections(rproc); |
192 | clean_minidump: |
193 | qcom_minidump_cleanup(rproc); |
194 | } |
195 | EXPORT_SYMBOL_GPL(qcom_minidump); |
196 | |
197 | static int glink_subdev_start(struct rproc_subdev *subdev) |
198 | { |
199 | struct qcom_rproc_glink *glink = to_glink_subdev(subdev); |
200 | |
201 | glink->edge = qcom_glink_smem_register(parent: glink->dev, node: glink->node); |
202 | |
203 | return PTR_ERR_OR_ZERO(ptr: glink->edge); |
204 | } |
205 | |
206 | static void glink_subdev_stop(struct rproc_subdev *subdev, bool crashed) |
207 | { |
208 | struct qcom_rproc_glink *glink = to_glink_subdev(subdev); |
209 | |
210 | qcom_glink_smem_unregister(glink: glink->edge); |
211 | glink->edge = NULL; |
212 | } |
213 | |
214 | static void glink_subdev_unprepare(struct rproc_subdev *subdev) |
215 | { |
216 | struct qcom_rproc_glink *glink = to_glink_subdev(subdev); |
217 | |
218 | qcom_glink_ssr_notify(ssr_name: glink->ssr_name); |
219 | } |
220 | |
221 | /** |
222 | * qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc |
223 | * @rproc: rproc handle to parent the subdevice |
224 | * @glink: reference to a GLINK subdev context |
225 | * @ssr_name: identifier of the associated remoteproc for ssr notifications |
226 | */ |
227 | void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink, |
228 | const char *ssr_name) |
229 | { |
230 | struct device *dev = &rproc->dev; |
231 | |
232 | glink->node = of_get_child_by_name(node: dev->parent->of_node, name: "glink-edge" ); |
233 | if (!glink->node) |
234 | return; |
235 | |
236 | glink->ssr_name = kstrdup_const(s: ssr_name, GFP_KERNEL); |
237 | if (!glink->ssr_name) |
238 | return; |
239 | |
240 | glink->dev = dev; |
241 | glink->subdev.start = glink_subdev_start; |
242 | glink->subdev.stop = glink_subdev_stop; |
243 | glink->subdev.unprepare = glink_subdev_unprepare; |
244 | |
245 | rproc_add_subdev(rproc, subdev: &glink->subdev); |
246 | } |
247 | EXPORT_SYMBOL_GPL(qcom_add_glink_subdev); |
248 | |
249 | /** |
250 | * qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc |
251 | * @rproc: rproc handle |
252 | * @glink: reference to a GLINK subdev context |
253 | */ |
254 | void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink) |
255 | { |
256 | if (!glink->node) |
257 | return; |
258 | |
259 | rproc_remove_subdev(rproc, subdev: &glink->subdev); |
260 | kfree_const(x: glink->ssr_name); |
261 | of_node_put(node: glink->node); |
262 | } |
263 | EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev); |
264 | |
265 | /** |
266 | * qcom_register_dump_segments() - register segments for coredump |
267 | * @rproc: remoteproc handle |
268 | * @fw: firmware header |
269 | * |
270 | * Register all segments of the ELF in the remoteproc coredump segment list |
271 | * |
272 | * Return: 0 on success, negative errno on failure. |
273 | */ |
274 | int qcom_register_dump_segments(struct rproc *rproc, |
275 | const struct firmware *fw) |
276 | { |
277 | const struct elf32_phdr *phdrs; |
278 | const struct elf32_phdr *phdr; |
279 | const struct elf32_hdr *ehdr; |
280 | int ret; |
281 | int i; |
282 | |
283 | ehdr = (struct elf32_hdr *)fw->data; |
284 | phdrs = (struct elf32_phdr *)(ehdr + 1); |
285 | |
286 | for (i = 0; i < ehdr->e_phnum; i++) { |
287 | phdr = &phdrs[i]; |
288 | |
289 | if (phdr->p_type != PT_LOAD) |
290 | continue; |
291 | |
292 | if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) |
293 | continue; |
294 | |
295 | if (!phdr->p_memsz) |
296 | continue; |
297 | |
298 | ret = rproc_coredump_add_segment(rproc, da: phdr->p_paddr, |
299 | size: phdr->p_memsz); |
300 | if (ret) |
301 | return ret; |
302 | } |
303 | |
304 | return 0; |
305 | } |
306 | EXPORT_SYMBOL_GPL(qcom_register_dump_segments); |
307 | |
308 | static int smd_subdev_start(struct rproc_subdev *subdev) |
309 | { |
310 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); |
311 | |
312 | smd->edge = qcom_smd_register_edge(parent: smd->dev, node: smd->node); |
313 | |
314 | return PTR_ERR_OR_ZERO(ptr: smd->edge); |
315 | } |
316 | |
317 | static void smd_subdev_stop(struct rproc_subdev *subdev, bool crashed) |
318 | { |
319 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); |
320 | |
321 | qcom_smd_unregister_edge(edge: smd->edge); |
322 | smd->edge = NULL; |
323 | } |
324 | |
325 | /** |
326 | * qcom_add_smd_subdev() - try to add a SMD subdevice to rproc |
327 | * @rproc: rproc handle to parent the subdevice |
328 | * @smd: reference to a Qualcomm subdev context |
329 | */ |
330 | void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) |
331 | { |
332 | struct device *dev = &rproc->dev; |
333 | |
334 | smd->node = of_get_child_by_name(node: dev->parent->of_node, name: "smd-edge" ); |
335 | if (!smd->node) |
336 | return; |
337 | |
338 | smd->dev = dev; |
339 | smd->subdev.start = smd_subdev_start; |
340 | smd->subdev.stop = smd_subdev_stop; |
341 | |
342 | rproc_add_subdev(rproc, subdev: &smd->subdev); |
343 | } |
344 | EXPORT_SYMBOL_GPL(qcom_add_smd_subdev); |
345 | |
346 | /** |
347 | * qcom_remove_smd_subdev() - remove the smd subdevice from rproc |
348 | * @rproc: rproc handle |
349 | * @smd: the SMD subdevice to remove |
350 | */ |
351 | void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) |
352 | { |
353 | if (!smd->node) |
354 | return; |
355 | |
356 | rproc_remove_subdev(rproc, subdev: &smd->subdev); |
357 | of_node_put(node: smd->node); |
358 | } |
359 | EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev); |
360 | |
361 | static struct qcom_ssr_subsystem *qcom_ssr_get_subsys(const char *name) |
362 | { |
363 | struct qcom_ssr_subsystem *info; |
364 | |
365 | mutex_lock(&qcom_ssr_subsys_lock); |
366 | /* Match in the global qcom_ssr_subsystem_list with name */ |
367 | list_for_each_entry(info, &qcom_ssr_subsystem_list, list) |
368 | if (!strcmp(info->name, name)) |
369 | goto out; |
370 | |
371 | info = kzalloc(size: sizeof(*info), GFP_KERNEL); |
372 | if (!info) { |
373 | info = ERR_PTR(error: -ENOMEM); |
374 | goto out; |
375 | } |
376 | info->name = kstrdup_const(s: name, GFP_KERNEL); |
377 | srcu_init_notifier_head(nh: &info->notifier_list); |
378 | |
379 | /* Add to global notification list */ |
380 | list_add_tail(new: &info->list, head: &qcom_ssr_subsystem_list); |
381 | |
382 | out: |
383 | mutex_unlock(lock: &qcom_ssr_subsys_lock); |
384 | return info; |
385 | } |
386 | |
387 | /** |
388 | * qcom_register_ssr_notifier() - register SSR notification handler |
389 | * @name: Subsystem's SSR name |
390 | * @nb: notifier_block to be invoked upon subsystem's state change |
391 | * |
392 | * This registers the @nb notifier block as part the notifier chain for a |
393 | * remoteproc associated with @name. The notifier block's callback |
394 | * will be invoked when the remote processor's SSR events occur |
395 | * (pre/post startup and pre/post shutdown). |
396 | * |
397 | * Return: a subsystem cookie on success, ERR_PTR on failure. |
398 | */ |
399 | void *qcom_register_ssr_notifier(const char *name, struct notifier_block *nb) |
400 | { |
401 | struct qcom_ssr_subsystem *info; |
402 | |
403 | info = qcom_ssr_get_subsys(name); |
404 | if (IS_ERR(ptr: info)) |
405 | return info; |
406 | |
407 | srcu_notifier_chain_register(nh: &info->notifier_list, nb); |
408 | |
409 | return &info->notifier_list; |
410 | } |
411 | EXPORT_SYMBOL_GPL(qcom_register_ssr_notifier); |
412 | |
413 | /** |
414 | * qcom_unregister_ssr_notifier() - unregister SSR notification handler |
415 | * @notify: subsystem cookie returned from qcom_register_ssr_notifier |
416 | * @nb: notifier_block to unregister |
417 | * |
418 | * This function will unregister the notifier from the particular notifier |
419 | * chain. |
420 | * |
421 | * Return: 0 on success, %ENOENT otherwise. |
422 | */ |
423 | int qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb) |
424 | { |
425 | return srcu_notifier_chain_unregister(nh: notify, nb); |
426 | } |
427 | EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier); |
428 | |
429 | static int ssr_notify_prepare(struct rproc_subdev *subdev) |
430 | { |
431 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); |
432 | struct qcom_ssr_notify_data data = { |
433 | .name = ssr->info->name, |
434 | .crashed = false, |
435 | }; |
436 | |
437 | srcu_notifier_call_chain(nh: &ssr->info->notifier_list, |
438 | val: QCOM_SSR_BEFORE_POWERUP, v: &data); |
439 | return 0; |
440 | } |
441 | |
442 | static int ssr_notify_start(struct rproc_subdev *subdev) |
443 | { |
444 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); |
445 | struct qcom_ssr_notify_data data = { |
446 | .name = ssr->info->name, |
447 | .crashed = false, |
448 | }; |
449 | |
450 | srcu_notifier_call_chain(nh: &ssr->info->notifier_list, |
451 | val: QCOM_SSR_AFTER_POWERUP, v: &data); |
452 | return 0; |
453 | } |
454 | |
455 | static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed) |
456 | { |
457 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); |
458 | struct qcom_ssr_notify_data data = { |
459 | .name = ssr->info->name, |
460 | .crashed = crashed, |
461 | }; |
462 | |
463 | srcu_notifier_call_chain(nh: &ssr->info->notifier_list, |
464 | val: QCOM_SSR_BEFORE_SHUTDOWN, v: &data); |
465 | } |
466 | |
467 | static void ssr_notify_unprepare(struct rproc_subdev *subdev) |
468 | { |
469 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); |
470 | struct qcom_ssr_notify_data data = { |
471 | .name = ssr->info->name, |
472 | .crashed = false, |
473 | }; |
474 | |
475 | srcu_notifier_call_chain(nh: &ssr->info->notifier_list, |
476 | val: QCOM_SSR_AFTER_SHUTDOWN, v: &data); |
477 | } |
478 | |
479 | /** |
480 | * qcom_add_ssr_subdev() - register subdevice as restart notification source |
481 | * @rproc: rproc handle |
482 | * @ssr: SSR subdevice handle |
483 | * @ssr_name: identifier to use for notifications originating from @rproc |
484 | * |
485 | * As the @ssr is registered with the @rproc SSR events will be sent to all |
486 | * registered listeners for the remoteproc when it's SSR events occur |
487 | * (pre/post startup and pre/post shutdown). |
488 | */ |
489 | void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr, |
490 | const char *ssr_name) |
491 | { |
492 | struct qcom_ssr_subsystem *info; |
493 | |
494 | info = qcom_ssr_get_subsys(name: ssr_name); |
495 | if (IS_ERR(ptr: info)) { |
496 | dev_err(&rproc->dev, "Failed to add ssr subdevice\n" ); |
497 | return; |
498 | } |
499 | |
500 | ssr->info = info; |
501 | ssr->subdev.prepare = ssr_notify_prepare; |
502 | ssr->subdev.start = ssr_notify_start; |
503 | ssr->subdev.stop = ssr_notify_stop; |
504 | ssr->subdev.unprepare = ssr_notify_unprepare; |
505 | |
506 | rproc_add_subdev(rproc, subdev: &ssr->subdev); |
507 | } |
508 | EXPORT_SYMBOL_GPL(qcom_add_ssr_subdev); |
509 | |
510 | /** |
511 | * qcom_remove_ssr_subdev() - remove subdevice as restart notification source |
512 | * @rproc: rproc handle |
513 | * @ssr: SSR subdevice handle |
514 | */ |
515 | void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr) |
516 | { |
517 | rproc_remove_subdev(rproc, subdev: &ssr->subdev); |
518 | ssr->info = NULL; |
519 | } |
520 | EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev); |
521 | |
522 | MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver" ); |
523 | MODULE_LICENSE("GPL v2" ); |
524 | |