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 28 struct qcom_ssr_subsystem { 29 const char *name; 30 struct srcu_notifier_head notifier_list; 31 struct list_head list; 32 }; 33 34 static LIST_HEAD(qcom_ssr_subsystem_list); 35 static DEFINE_MUTEX(qcom_ssr_subsys_lock); 36 37 static 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 46 static 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 54 static 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 */ 67 void 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 } 87 EXPORT_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 */ 94 void 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 } 103 EXPORT_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 */ 114 int 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 } 146 EXPORT_SYMBOL_GPL(qcom_register_dump_segments); 147 148 static 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 157 static 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 */ 170 void 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 } 184 EXPORT_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 */ 191 void 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 } 199 EXPORT_SYMBOL_GPL(qcom_remove_smd_subdev); 200 201 static 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 222 out: 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 */ 239 void *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 } 251 EXPORT_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 */ 263 int qcom_unregister_ssr_notifier(void *notify, struct notifier_block *nb) 264 { 265 return srcu_notifier_chain_unregister(notify, nb); 266 } 267 EXPORT_SYMBOL_GPL(qcom_unregister_ssr_notifier); 268 269 static 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 282 static 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 295 static 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 307 static 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 */ 329 void 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 } 348 EXPORT_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 */ 355 void 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 } 360 EXPORT_SYMBOL_GPL(qcom_remove_ssr_subdev); 361 362 MODULE_DESCRIPTION("Qualcomm Remoteproc helper driver"); 363 MODULE_LICENSE("GPL v2"); 364