linux/drivers/remoteproc/qcom_common.c
<<
>>
Prefs
   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
  21#include "remoteproc_internal.h"
  22#include "qcom_common.h"
  23
  24#define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)
  25#define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
  26#define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
  27
  28struct qcom_ssr_subsystem {
  29        const char *name;
  30        struct srcu_notifier_head notifier_list;
  31        struct list_head list;
  32};
  33
  34static LIST_HEAD(qcom_ssr_subsystem_list);
  35static DEFINE_MUTEX(qcom_ssr_subsys_lock);
  36
  37static int glink_subdev_start(struct rproc_subdev *subdev)
  38{
  39        struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
  40
  41        glink->edge = qcom_glink_smem_register(glink->dev, glink->node);
  42
  43        return PTR_ERR_OR_ZERO(glink->edge);
  44}
  45
  46static void glink_subdev_stop(struct rproc_subdev *subdev, bool crashed)
  47{
  48        struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
  49
  50        qcom_glink_smem_unregister(glink->edge);
  51        glink->edge = NULL;
  52}
  53
  54static void glink_subdev_unprepare(struct rproc_subdev *subdev)
  55{
  56        struct qcom_rproc_glink *glink = to_glink_subdev(subdev);
  57
  58        qcom_glink_ssr_notify(glink->ssr_name);
  59}
  60
  61/**
  62 * qcom_add_glink_subdev() - try to add a GLINK subdevice to rproc
  63 * @rproc:      rproc handle to parent the subdevice
  64 * @glink:      reference to a GLINK subdev context
  65 * @ssr_name:   identifier of the associated remoteproc for ssr notifications
  66 */
  67void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,
  68                           const char *ssr_name)
  69{
  70        struct device *dev = &rproc->dev;
  71
  72        glink->node = of_get_child_by_name(dev->parent->of_node, "glink-edge");
  73        if (!glink->node)
  74                return;
  75
  76        glink->ssr_name = kstrdup_const(ssr_name, GFP_KERNEL);
  77        if (!glink->ssr_name)
  78                return;
  79
  80        glink->dev = dev;
  81        glink->subdev.start = glink_subdev_start;
  82        glink->subdev.stop = glink_subdev_stop;
  83        glink->subdev.unprepare = glink_subdev_unprepare;
  84
  85        rproc_add_subdev(rproc, &glink->subdev);
  86}
  87EXPORT_SYMBOL_GPL(qcom_add_glink_subdev);
  88
  89/**
  90 * qcom_remove_glink_subdev() - remove a GLINK subdevice from rproc
  91 * @rproc:      rproc handle
  92 * @glink:      reference to a GLINK subdev context
  93 */
  94void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
  95{
  96        if (!glink->node)
  97                return;
  98
  99        rproc_remove_subdev(rproc, &glink->subdev);
 100        kfree_const(glink->ssr_name);
 101        of_node_put(glink->node);
 102}
 103EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);
 104
 105/**
 106 * qcom_register_dump_segments() - register segments for coredump
 107 * @rproc:      remoteproc handle
 108 * @fw:         firmware header
 109 *
 110 * Register all segments of the ELF in the remoteproc coredump segment list
 111 *
 112 * Return: 0 on success, negative errno on failure.
 113 */
 114int qcom_register_dump_segments(struct rproc *rproc,
 115                                const struct firmware *fw)
 116{
 117        const struct elf32_phdr *phdrs;
 118        const struct elf32_phdr *phdr;
 119        const struct elf32_hdr *ehdr;
 120        int ret;
 121        int i;
 122
 123        ehdr = (struct elf32_hdr *)fw->data;
 124        phdrs = (struct elf32_phdr *)(ehdr + 1);
 125
 126        for (i = 0; i < ehdr->e_phnum; i++) {
 127                phdr = &phdrs[i];
 128
 129                if (phdr->p_type != PT_LOAD)
 130                        continue;
 131
 132                if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH)
 133                        continue;
 134
 135                if (!phdr->p_memsz)
 136                        continue;
 137
 138                ret = rproc_coredump_add_segment(rproc, phdr->p_paddr,
 139                                                 phdr->p_memsz);
 140                if (ret)
 141                        return ret;
 142        }
 143
 144        return 0;
 145}
 146EXPORT_SYMBOL_GPL(qcom_register_dump_segments);
 147
 148static int smd_subdev_start(struct rproc_subdev *subdev)
 149{
 150        struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
 151
 152        smd->edge = qcom_smd_register_edge(smd->dev, smd->node);
 153
 154        return PTR_ERR_OR_ZERO(smd->edge);
 155}
 156
 157static void smd_subdev_stop(struct rproc_subdev *subdev, bool crashed)
 158{
 159        struct qcom_rproc_subdev *smd = to_smd_subdev(subdev);
 160
 161        qcom_smd_unregister_edge(smd->edge);
 162        smd->edge = NULL;
 163}
 164
 165/**
 166 * qcom_add_smd_subdev() - try to add a SMD subdevice to rproc
 167 * @rproc:      rproc handle to parent the subdevice
 168 * @smd:        reference to a Qualcomm subdev context
 169 */
 170void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
 171{
 172        struct device *dev = &rproc->dev;
 173
 174        smd->node = of_get_child_by_name(dev->parent->of_node, "smd-edge");
 175        if (!smd->node)
 176                return;
 177
 178        smd->dev = dev;
 179        smd->subdev.start = smd_subdev_start;
 180        smd->subdev.stop = smd_subdev_stop;
 181
 182        rproc_add_subdev(rproc, &smd->subdev);
 183}
 184EXPORT_SYMBOL_GPL(qcom_add_smd_subdev);
 185
 186/**
 187 * qcom_remove_smd_subdev() - remove the smd subdevice from rproc
 188 * @rproc:      rproc handle
 189 * @smd:        the SMD subdevice to remove
 190 */
 191void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
 192{
 193        if (!smd->node)
 194                return;
 195
 196        rproc_remove_subdev(rproc, &smd->subdev);
 197        of_node_put(smd->node);
 198}
 199EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev);
 200
 201static struct qcom_ssr_subsystem *qcom_ssr_get_subsys(const char *name)
 202{
 203        struct qcom_ssr_subsystem *info;
 204
 205        mutex_lock(&qcom_ssr_subsys_lock);
 206        /* Match in the global qcom_ssr_subsystem_list with name */
 207        list_for_each_entry(info, &qcom_ssr_subsystem_list, list)
 208                if (!strcmp(info->name, name))
 209                        goto out;
 210
 211        info = kzalloc(sizeof(*info), GFP_KERNEL);
 212        if (!info) {
 213                info = ERR_PTR(-ENOMEM);
 214                goto out;
 215        }
 216        info->name = kstrdup_const(name, GFP_KERNEL);
 217        srcu_init_notifier_head(&info->notifier_list);
 218
 219        /* Add to global notification list */
 220        list_add_tail(&info->list, &qcom_ssr_subsystem_list);
 221
 222out:
 223        mutex_unlock(&qcom_ssr_subsys_lock);
 224        return info;
 225}
 226
 227/**
 228 * qcom_register_ssr_notifier() - register SSR notification handler
 229 * @name:       Subsystem's SSR name
 230 * @nb:         notifier_block to be invoked upon subsystem's state change
 231 *
 232 * This registers the @nb notifier block as part the notifier chain for a
 233 * remoteproc associated with @name. The notifier block's callback
 234 * will be invoked when the remote processor's SSR events occur
 235 * (pre/post startup and pre/post shutdown).
 236 *
 237 * Return: a subsystem cookie on success, ERR_PTR on failure.
 238 */
 239void *qcom_register_ssr_notifier(const char *name, struct notifier_block *nb)
 240{
 241        struct qcom_ssr_subsystem *info;
 242
 243        info = qcom_ssr_get_subsys(name);
 244        if (IS_ERR(info))
 245                return info;
 246
 247        srcu_notifier_chain_register(&info->notifier_list, nb);
 248
 249        return &info->notifier_list;
 250}
 251EXPORT_SYMBOL_GPL(qcom_register_ssr_notifier);
 252
 253/**
 254 * qcom_unregister_ssr_notifier() - unregister SSR notification handler
 255 * @notify:     subsystem cookie returned from qcom_register_ssr_notifier
 256 * @nb:         notifier_block to unregister
 257 *
 258 * This function will unregister the notifier from the particular notifier
 259 * chain.
 260 *
 261 * Return: 0 on success, %ENOENT otherwise.
 262 */
 263int qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb)
 264{
 265        return srcu_notifier_chain_unregister(notify, nb);
 266}
 267EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier);
 268
 269static int ssr_notify_prepare(struct rproc_subdev *subdev)
 270{
 271        struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
 272        struct qcom_ssr_notify_data data = {
 273                .name = ssr->info->name,
 274                .crashed = false,
 275        };
 276
 277        srcu_notifier_call_chain(&ssr->info->notifier_list,
 278                                 QCOM_SSR_BEFORE_POWERUP, &data);
 279        return 0;
 280}
 281
 282static int ssr_notify_start(struct rproc_subdev *subdev)
 283{
 284        struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
 285        struct qcom_ssr_notify_data data = {
 286                .name = ssr->info->name,
 287                .crashed = false,
 288        };
 289
 290        srcu_notifier_call_chain(&ssr->info->notifier_list,
 291                                 QCOM_SSR_AFTER_POWERUP, &data);
 292        return 0;
 293}
 294
 295static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed)
 296{
 297        struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
 298        struct qcom_ssr_notify_data data = {
 299                .name = ssr->info->name,
 300                .crashed = crashed,
 301        };
 302
 303        srcu_notifier_call_chain(&ssr->info->notifier_list,
 304                                 QCOM_SSR_BEFORE_SHUTDOWN, &data);
 305}
 306
 307static void ssr_notify_unprepare(struct rproc_subdev *subdev)
 308{
 309        struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
 310        struct qcom_ssr_notify_data data = {
 311                .name = ssr->info->name,
 312                .crashed = false,
 313        };
 314
 315        srcu_notifier_call_chain(&ssr->info->notifier_list,
 316                                 QCOM_SSR_AFTER_SHUTDOWN, &data);
 317}
 318
 319/**
 320 * qcom_add_ssr_subdev() - register subdevice as restart notification source
 321 * @rproc:      rproc handle
 322 * @ssr:        SSR subdevice handle
 323 * @ssr_name:   identifier to use for notifications originating from @rproc
 324 *
 325 * As the @ssr is registered with the @rproc SSR events will be sent to all
 326 * registered listeners for the remoteproc when it's SSR events occur
 327 * (pre/post startup and pre/post shutdown).
 328 */
 329void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr,
 330                         const char *ssr_name)
 331{
 332        struct qcom_ssr_subsystem *info;
 333
 334        info = qcom_ssr_get_subsys(ssr_name);
 335        if (IS_ERR(info)) {
 336                dev_err(&rproc->dev, "Failed to add ssr subdevice\n");
 337                return;
 338        }
 339
 340        ssr->info = info;
 341        ssr->subdev.prepare = ssr_notify_prepare;
 342        ssr->subdev.start = ssr_notify_start;
 343        ssr->subdev.stop = ssr_notify_stop;
 344        ssr->subdev.unprepare = ssr_notify_unprepare;
 345
 346        rproc_add_subdev(rproc, &ssr->subdev);
 347}
 348EXPORT_SYMBOL_GPL(qcom_add_ssr_subdev);
 349
 350/**
 351 * qcom_remove_ssr_subdev() - remove subdevice as restart notification source
 352 * @rproc:      rproc handle
 353 * @ssr:        SSR subdevice handle
 354 */
 355void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr)
 356{
 357        rproc_remove_subdev(rproc, &ssr->subdev);
 358        ssr->info = NULL;
 359}
 360EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev);
 361
 362MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver");
 363MODULE_LICENSE("GPL v2");
 364