611 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Qualcomm Peripheral Image Loader helpers4 *5 * Copyright (C) 2016 Linaro Ltd6 * Copyright (C) 2015 Sony Mobile Communications Inc7 * 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/auxiliary_bus.h>17#include <linux/rpmsg/qcom_glink.h>18#include <linux/rpmsg/qcom_smd.h>19#include <linux/slab.h>20#include <linux/soc/qcom/mdt_loader.h>21#include <linux/soc/qcom/smem.h>22 23#include "remoteproc_internal.h"24#include "qcom_common.h"25 26#define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)27#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)28#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)29#define to_pdm_subdev(d) container_of(d, struct qcom_rproc_pdm, subdev)30 31#define MAX_NUM_OF_SS 1032#define MAX_REGION_NAME_LENGTH 1633#define SBL_MINIDUMP_SMEM_ID 60234#define MINIDUMP_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)35#define MINIDUMP_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)36#define MINIDUMP_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0)37 38/**39 * struct minidump_region - Minidump region40 * @name : Name of the region to be dumped41 * @seq_num: : Use to differentiate regions with same name.42 * @valid : This entry to be dumped (if set to 1)43 * @address : Physical address of region to be dumped44 * @size : Size of the region45 */46struct minidump_region {47 char name[MAX_REGION_NAME_LENGTH];48 __le32 seq_num;49 __le32 valid;50 __le64 address;51 __le64 size;52};53 54/**55 * struct minidump_subsystem - Subsystem's SMEM Table of content56 * @status : Subsystem toc init status57 * @enabled : if set to 1, this region would be copied during coredump58 * @encryption_status: Encryption status for this subsystem59 * @encryption_required : Decides to encrypt the subsystem regions or not60 * @region_count : Number of regions added in this subsystem toc61 * @regions_baseptr : regions base pointer of the subsystem62 */63struct minidump_subsystem {64 __le32 status;65 __le32 enabled;66 __le32 encryption_status;67 __le32 encryption_required;68 __le32 region_count;69 __le64 regions_baseptr;70};71 72/**73 * struct minidump_global_toc - Global Table of Content74 * @status : Global Minidump init status75 * @md_revision : Minidump revision76 * @enabled : Minidump enable status77 * @subsystems : Array of subsystems toc78 */79struct minidump_global_toc {80 __le32 status;81 __le32 md_revision;82 __le32 enabled;83 struct minidump_subsystem subsystems[MAX_NUM_OF_SS];84};85 86struct qcom_ssr_subsystem {87 const char *name;88 struct srcu_notifier_head notifier_list;89 struct list_head list;90};91 92static LIST_HEAD(qcom_ssr_subsystem_list);93static DEFINE_MUTEX(qcom_ssr_subsys_lock);94 95static void qcom_minidump_cleanup(struct rproc *rproc)96{97 struct rproc_dump_segment *entry, *tmp;98 99 list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) {100 list_del(&entry->node);101 kfree(entry->priv);102 kfree(entry);103 }104}105 106static int qcom_add_minidump_segments(struct rproc *rproc, struct minidump_subsystem *subsystem,107 void (*rproc_dumpfn_t)(struct rproc *rproc, struct rproc_dump_segment *segment,108 void *dest, size_t offset, size_t size))109{110 struct minidump_region __iomem *ptr;111 struct minidump_region region;112 int seg_cnt, i;113 dma_addr_t da;114 size_t size;115 char *name;116 117 if (WARN_ON(!list_empty(&rproc->dump_segments))) {118 dev_err(&rproc->dev, "dump segment list already populated\n");119 return -EUCLEAN;120 }121 122 seg_cnt = le32_to_cpu(subsystem->region_count);123 ptr = ioremap((unsigned long)le64_to_cpu(subsystem->regions_baseptr),124 seg_cnt * sizeof(struct minidump_region));125 if (!ptr)126 return -EFAULT;127 128 for (i = 0; i < seg_cnt; i++) {129 memcpy_fromio(®ion, ptr + i, sizeof(region));130 if (le32_to_cpu(region.valid) == MINIDUMP_REGION_VALID) {131 name = kstrndup(region.name, MAX_REGION_NAME_LENGTH - 1, GFP_KERNEL);132 if (!name) {133 iounmap(ptr);134 return -ENOMEM;135 }136 da = le64_to_cpu(region.address);137 size = le64_to_cpu(region.size);138 rproc_coredump_add_custom_segment(rproc, da, size, rproc_dumpfn_t, name);139 }140 }141 142 iounmap(ptr);143 return 0;144}145 146void qcom_minidump(struct rproc *rproc, unsigned int minidump_id,147 void (*rproc_dumpfn_t)(struct rproc *rproc,148 struct rproc_dump_segment *segment, void *dest, size_t offset,149 size_t size))150{151 int ret;152 struct minidump_subsystem *subsystem;153 struct minidump_global_toc *toc;154 155 /* Get Global minidump ToC*/156 toc = qcom_smem_get(QCOM_SMEM_HOST_ANY, SBL_MINIDUMP_SMEM_ID, NULL);157 158 /* check if global table pointer exists and init is set */159 if (IS_ERR(toc) || !toc->status) {160 dev_err(&rproc->dev, "Minidump TOC not found in SMEM\n");161 return;162 }163 164 /* Get subsystem table of contents using the minidump id */165 subsystem = &toc->subsystems[minidump_id];166 167 /**168 * Collect minidump if SS ToC is valid and segment table169 * is initialized in memory and encryption status is set.170 */171 if (subsystem->regions_baseptr == 0 ||172 le32_to_cpu(subsystem->status) != 1 ||173 le32_to_cpu(subsystem->enabled) != MINIDUMP_SS_ENABLED) {174 return rproc_coredump(rproc);175 }176 177 if (le32_to_cpu(subsystem->encryption_status) != MINIDUMP_SS_ENCR_DONE) {178 dev_err(&rproc->dev, "Minidump not ready, skipping\n");179 return;180 }181 182 /**183 * Clear out the dump segments populated by parse_fw before184 * re-populating them with minidump segments.185 */186 rproc_coredump_cleanup(rproc);187 188 ret = qcom_add_minidump_segments(rproc, subsystem, rproc_dumpfn_t);189 if (ret) {190 dev_err(&rproc->dev, "Failed with error: %d while adding minidump entries\n", ret);191 goto clean_minidump;192 }193 rproc_coredump_using_sections(rproc);194clean_minidump:195 qcom_minidump_cleanup(rproc);196}197EXPORT_SYMBOL_GPL(qcom_minidump);198 199static int glink_subdev_start(struct rproc_subdev *subdev)200{201 struct qcom_rproc_glink *glink = to_glink_subdev(subdev);202 203 glink->edge = qcom_glink_smem_register(glink->dev, glink->node);204 205 return PTR_ERR_OR_ZERO(glink->edge);206}207 208static void glink_subdev_stop(struct rproc_subdev *subdev, bool crashed)209{210 struct qcom_rproc_glink *glink = to_glink_subdev(subdev);211 212 qcom_glink_smem_unregister(glink->edge);213 glink->edge = NULL;214}215 216static void glink_subdev_unprepare(struct rproc_subdev *subdev)217{218 struct qcom_rproc_glink *glink = to_glink_subdev(subdev);219 220 qcom_glink_ssr_notify(glink->ssr_name);221}222 223/**224 * qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc225 * @rproc: rproc handle to parent the subdevice226 * @glink: reference to a GLINK subdev context227 * @ssr_name: identifier of the associated remoteproc for ssr notifications228 */229void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,230 const char *ssr_name)231{232 struct device *dev = &rproc->dev;233 234 glink->node = of_get_child_by_name(dev->parent->of_node, "glink-edge");235 if (!glink->node)236 return;237 238 glink->ssr_name = kstrdup_const(ssr_name, GFP_KERNEL);239 if (!glink->ssr_name)240 return;241 242 glink->dev = dev;243 glink->subdev.start = glink_subdev_start;244 glink->subdev.stop = glink_subdev_stop;245 glink->subdev.unprepare = glink_subdev_unprepare;246 247 rproc_add_subdev(rproc, &glink->subdev);248}249EXPORT_SYMBOL_GPL(qcom_add_glink_subdev);250 251/**252 * qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc253 * @rproc: rproc handle254 * @glink: reference to a GLINK subdev context255 */256void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)257{258 if (!glink->node)259 return;260 261 rproc_remove_subdev(rproc, &glink->subdev);262 kfree_const(glink->ssr_name);263 of_node_put(glink->node);264}265EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);266 267/**268 * qcom_register_dump_segments() - register segments for coredump269 * @rproc: remoteproc handle270 * @fw: firmware header271 *272 * Register all segments of the ELF in the remoteproc coredump segment list273 *274 * Return: 0 on success, negative errno on failure.275 */276int qcom_register_dump_segments(struct rproc *rproc,277 const struct firmware *fw)278{279 const struct elf32_phdr *phdrs;280 const struct elf32_phdr *phdr;281 const struct elf32_hdr *ehdr;282 int ret;283 int i;284 285 ehdr = (struct elf32_hdr *)fw->data;286 phdrs = (struct elf32_phdr *)(ehdr + 1);287 288 for (i = 0; i < ehdr->e_phnum; i++) {289 phdr = &phdrs[i];290 291 if (phdr->p_type != PT_LOAD)292 continue;293 294 if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)295 continue;296 297 if (!phdr->p_memsz)298 continue;299 300 ret = rproc_coredump_add_segment(rproc, phdr->p_paddr,301 phdr->p_memsz);302 if (ret)303 return ret;304 }305 306 return 0;307}308EXPORT_SYMBOL_GPL(qcom_register_dump_segments);309 310static int smd_subdev_start(struct rproc_subdev *subdev)311{312 struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);313 314 smd->edge = qcom_smd_register_edge(smd->dev, smd->node);315 316 return PTR_ERR_OR_ZERO(smd->edge);317}318 319static void smd_subdev_stop(struct rproc_subdev *subdev, bool crashed)320{321 struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);322 323 qcom_smd_unregister_edge(smd->edge);324 smd->edge = NULL;325}326 327/**328 * qcom_add_smd_subdev() - try to add a SMD subdevice to rproc329 * @rproc: rproc handle to parent the subdevice330 * @smd: reference to a Qualcomm subdev context331 */332void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)333{334 struct device *dev = &rproc->dev;335 336 smd->node = of_get_child_by_name(dev->parent->of_node, "smd-edge");337 if (!smd->node)338 return;339 340 smd->dev = dev;341 smd->subdev.start = smd_subdev_start;342 smd->subdev.stop = smd_subdev_stop;343 344 rproc_add_subdev(rproc, &smd->subdev);345}346EXPORT_SYMBOL_GPL(qcom_add_smd_subdev);347 348/**349 * qcom_remove_smd_subdev() - remove the smd subdevice from rproc350 * @rproc: rproc handle351 * @smd: the SMD subdevice to remove352 */353void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)354{355 if (!smd->node)356 return;357 358 rproc_remove_subdev(rproc, &smd->subdev);359 of_node_put(smd->node);360}361EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);362 363static struct qcom_ssr_subsystem *qcom_ssr_get_subsys(const char *name)364{365 struct qcom_ssr_subsystem *info;366 367 mutex_lock(&qcom_ssr_subsys_lock);368 /* Match in the global qcom_ssr_subsystem_list with name */369 list_for_each_entry(info, &qcom_ssr_subsystem_list, list)370 if (!strcmp(info->name, name))371 goto out;372 373 info = kzalloc(sizeof(*info), GFP_KERNEL);374 if (!info) {375 info = ERR_PTR(-ENOMEM);376 goto out;377 }378 info->name = kstrdup_const(name, GFP_KERNEL);379 srcu_init_notifier_head(&info->notifier_list);380 381 /* Add to global notification list */382 list_add_tail(&info->list, &qcom_ssr_subsystem_list);383 384out:385 mutex_unlock(&qcom_ssr_subsys_lock);386 return info;387}388 389/**390 * qcom_register_ssr_notifier() - register SSR notification handler391 * @name: Subsystem's SSR name392 * @nb: notifier_block to be invoked upon subsystem's state change393 *394 * This registers the @nb notifier block as part the notifier chain for a395 * remoteproc associated with @name. The notifier block's callback396 * will be invoked when the remote processor's SSR events occur397 * (pre/post startup and pre/post shutdown).398 *399 * Return: a subsystem cookie on success, ERR_PTR on failure.400 */401void *qcom_register_ssr_notifier(const char *name, struct notifier_block *nb)402{403 struct qcom_ssr_subsystem *info;404 405 info = qcom_ssr_get_subsys(name);406 if (IS_ERR(info))407 return info;408 409 srcu_notifier_chain_register(&info->notifier_list, nb);410 411 return &info->notifier_list;412}413EXPORT_SYMBOL_GPL(qcom_register_ssr_notifier);414 415/**416 * qcom_unregister_ssr_notifier() - unregister SSR notification handler417 * @notify: subsystem cookie returned from qcom_register_ssr_notifier418 * @nb: notifier_block to unregister419 *420 * This function will unregister the notifier from the particular notifier421 * chain.422 *423 * Return: 0 on success, %ENOENT otherwise.424 */425int qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb)426{427 return srcu_notifier_chain_unregister(notify, nb);428}429EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier);430 431static int ssr_notify_prepare(struct rproc_subdev *subdev)432{433 struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);434 struct qcom_ssr_notify_data data = {435 .name = ssr->info->name,436 .crashed = false,437 };438 439 srcu_notifier_call_chain(&ssr->info->notifier_list,440 QCOM_SSR_BEFORE_POWERUP, &data);441 return 0;442}443 444static int ssr_notify_start(struct rproc_subdev *subdev)445{446 struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);447 struct qcom_ssr_notify_data data = {448 .name = ssr->info->name,449 .crashed = false,450 };451 452 srcu_notifier_call_chain(&ssr->info->notifier_list,453 QCOM_SSR_AFTER_POWERUP, &data);454 return 0;455}456 457static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed)458{459 struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);460 struct qcom_ssr_notify_data data = {461 .name = ssr->info->name,462 .crashed = crashed,463 };464 465 srcu_notifier_call_chain(&ssr->info->notifier_list,466 QCOM_SSR_BEFORE_SHUTDOWN, &data);467}468 469static void ssr_notify_unprepare(struct rproc_subdev *subdev)470{471 struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);472 struct qcom_ssr_notify_data data = {473 .name = ssr->info->name,474 .crashed = false,475 };476 477 srcu_notifier_call_chain(&ssr->info->notifier_list,478 QCOM_SSR_AFTER_SHUTDOWN, &data);479}480 481/**482 * qcom_add_ssr_subdev() - register subdevice as restart notification source483 * @rproc: rproc handle484 * @ssr: SSR subdevice handle485 * @ssr_name: identifier to use for notifications originating from @rproc486 *487 * As the @ssr is registered with the @rproc SSR events will be sent to all488 * registered listeners for the remoteproc when it's SSR events occur489 * (pre/post startup and pre/post shutdown).490 */491void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,492 const char *ssr_name)493{494 struct qcom_ssr_subsystem *info;495 496 info = qcom_ssr_get_subsys(ssr_name);497 if (IS_ERR(info)) {498 dev_err(&rproc->dev, "Failed to add ssr subdevice\n");499 return;500 }501 502 ssr->info = info;503 ssr->subdev.prepare = ssr_notify_prepare;504 ssr->subdev.start = ssr_notify_start;505 ssr->subdev.stop = ssr_notify_stop;506 ssr->subdev.unprepare = ssr_notify_unprepare;507 508 rproc_add_subdev(rproc, &ssr->subdev);509}510EXPORT_SYMBOL_GPL(qcom_add_ssr_subdev);511 512/**513 * qcom_remove_ssr_subdev() - remove subdevice as restart notification source514 * @rproc: rproc handle515 * @ssr: SSR subdevice handle516 */517void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)518{519 rproc_remove_subdev(rproc, &ssr->subdev);520 ssr->info = NULL;521}522EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);523 524static void pdm_dev_release(struct device *dev)525{526 struct auxiliary_device *adev = to_auxiliary_dev(dev);527 528 kfree(adev);529}530 531static int pdm_notify_prepare(struct rproc_subdev *subdev)532{533 struct qcom_rproc_pdm *pdm = to_pdm_subdev(subdev);534 struct auxiliary_device *adev;535 int ret;536 537 adev = kzalloc(sizeof(*adev), GFP_KERNEL);538 if (!adev)539 return -ENOMEM;540 541 adev->dev.parent = pdm->dev;542 adev->dev.release = pdm_dev_release;543 adev->name = "pd-mapper";544 adev->id = pdm->index;545 546 ret = auxiliary_device_init(adev);547 if (ret) {548 kfree(adev);549 return ret;550 }551 552 ret = auxiliary_device_add(adev);553 if (ret) {554 auxiliary_device_uninit(adev);555 return ret;556 }557 558 pdm->adev = adev;559 560 return 0;561}562 563 564static void pdm_notify_unprepare(struct rproc_subdev *subdev)565{566 struct qcom_rproc_pdm *pdm = to_pdm_subdev(subdev);567 568 if (!pdm->adev)569 return;570 571 auxiliary_device_delete(pdm->adev);572 auxiliary_device_uninit(pdm->adev);573 pdm->adev = NULL;574}575 576/**577 * qcom_add_pdm_subdev() - register PD Mapper subdevice578 * @rproc: rproc handle579 * @pdm: PDM subdevice handle580 *581 * Register @pdm so that Protection Device mapper service is started when the582 * DSP is started too.583 */584void qcom_add_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm)585{586 pdm->dev = &rproc->dev;587 pdm->index = rproc->index;588 589 pdm->subdev.prepare = pdm_notify_prepare;590 pdm->subdev.unprepare = pdm_notify_unprepare;591 592 rproc_add_subdev(rproc, &pdm->subdev);593}594EXPORT_SYMBOL_GPL(qcom_add_pdm_subdev);595 596/**597 * qcom_remove_pdm_subdev() - remove PD Mapper subdevice598 * @rproc: rproc handle599 * @pdm: PDM subdevice handle600 *601 * Remove the PD Mapper subdevice.602 */603void qcom_remove_pdm_subdev(struct rproc *rproc, struct qcom_rproc_pdm *pdm)604{605 rproc_remove_subdev(rproc, &pdm->subdev);606}607EXPORT_SYMBOL_GPL(qcom_remove_pdm_subdev);608 609MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");610MODULE_LICENSE("GPL v2");611