diff options
-rw-r--r-- | drivers/gpu/drm/msm/adreno/a5xx_gpu.c | 4 | ||||
-rw-r--r-- | drivers/media/platform/qcom/venus/firmware.c | 2 | ||||
-rw-r--r-- | drivers/remoteproc/Kconfig | 19 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 1 | ||||
-rw-r--r-- | drivers/remoteproc/imx_rproc.c | 23 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_adsp_pil.c | 20 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_common.c | 56 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_common.h | 23 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_q6v5_pil.c | 9 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_sysmon.c | 579 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_wcnss.c | 11 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 152 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_internal.h | 7 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/mdt_loader.c | 7 | ||||
-rw-r--r-- | include/linux/remoteproc.h | 27 | ||||
-rw-r--r-- | include/linux/soc/qcom/mdt_loader.h | 3 | ||||
-rw-r--r-- | samples/Kconfig | 10 | ||||
-rw-r--r-- | samples/Makefile | 2 | ||||
-rw-r--r-- | samples/qmi/Makefile | 1 | ||||
-rw-r--r-- | samples/qmi/qmi_sample_client.c | 622 |
21 files changed, 1524 insertions, 56 deletions
diff --git a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c index a4f68affc13b..d39400e5bc42 100644 --- a/drivers/gpu/drm/msm/adreno/a5xx_gpu.c +++ b/drivers/gpu/drm/msm/adreno/a5xx_gpu.c | |||
@@ -89,14 +89,14 @@ static int zap_shader_load_mdt(struct msm_gpu *gpu, const char *fwname) | |||
89 | */ | 89 | */ |
90 | if (to_adreno_gpu(gpu)->fwloc == FW_LOCATION_LEGACY) { | 90 | if (to_adreno_gpu(gpu)->fwloc == FW_LOCATION_LEGACY) { |
91 | ret = qcom_mdt_load(dev, fw, fwname, GPU_PAS_ID, | 91 | ret = qcom_mdt_load(dev, fw, fwname, GPU_PAS_ID, |
92 | mem_region, mem_phys, mem_size); | 92 | mem_region, mem_phys, mem_size, NULL); |
93 | } else { | 93 | } else { |
94 | char newname[strlen("qcom/") + strlen(fwname) + 1]; | 94 | char newname[strlen("qcom/") + strlen(fwname) + 1]; |
95 | 95 | ||
96 | sprintf(newname, "qcom/%s", fwname); | 96 | sprintf(newname, "qcom/%s", fwname); |
97 | 97 | ||
98 | ret = qcom_mdt_load(dev, fw, newname, GPU_PAS_ID, | 98 | ret = qcom_mdt_load(dev, fw, newname, GPU_PAS_ID, |
99 | mem_region, mem_phys, mem_size); | 99 | mem_region, mem_phys, mem_size, NULL); |
100 | } | 100 | } |
101 | if (ret) | 101 | if (ret) |
102 | goto out; | 102 | goto out; |
diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c index 521d4b36c090..c4a577848dd7 100644 --- a/drivers/media/platform/qcom/venus/firmware.c +++ b/drivers/media/platform/qcom/venus/firmware.c | |||
@@ -76,7 +76,7 @@ int venus_boot(struct device *dev, const char *fwname) | |||
76 | } | 76 | } |
77 | 77 | ||
78 | ret = qcom_mdt_load(dev, mdt, fwname, VENUS_PAS_ID, mem_va, mem_phys, | 78 | ret = qcom_mdt_load(dev, mdt, fwname, VENUS_PAS_ID, mem_va, mem_phys, |
79 | mem_size); | 79 | mem_size, NULL); |
80 | 80 | ||
81 | release_firmware(mdt); | 81 | release_firmware(mdt); |
82 | 82 | ||
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index b609e1d3654b..027274008b08 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig | |||
@@ -6,6 +6,7 @@ config REMOTEPROC | |||
6 | select CRC32 | 6 | select CRC32 |
7 | select FW_LOADER | 7 | select FW_LOADER |
8 | select VIRTIO | 8 | select VIRTIO |
9 | select WANT_DEV_COREDUMP | ||
9 | help | 10 | help |
10 | Support for remote processors (such as DSP coprocessors). These | 11 | Support for remote processors (such as DSP coprocessors). These |
11 | are mainly used on embedded systems. | 12 | are mainly used on embedded systems. |
@@ -90,6 +91,7 @@ config QCOM_ADSP_PIL | |||
90 | depends on QCOM_SMEM | 91 | depends on QCOM_SMEM |
91 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) | 92 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) |
92 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n | 93 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n |
94 | depends on QCOM_SYSMON || QCOM_SYSMON=n | ||
93 | select MFD_SYSCON | 95 | select MFD_SYSCON |
94 | select QCOM_MDT_LOADER | 96 | select QCOM_MDT_LOADER |
95 | select QCOM_RPROC_COMMON | 97 | select QCOM_RPROC_COMMON |
@@ -107,6 +109,7 @@ config QCOM_Q6V5_PIL | |||
107 | depends on QCOM_SMEM | 109 | depends on QCOM_SMEM |
108 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) | 110 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) |
109 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n | 111 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n |
112 | depends on QCOM_SYSMON || QCOM_SYSMON=n | ||
110 | select MFD_SYSCON | 113 | select MFD_SYSCON |
111 | select QCOM_RPROC_COMMON | 114 | select QCOM_RPROC_COMMON |
112 | select QCOM_SCM | 115 | select QCOM_SCM |
@@ -114,12 +117,28 @@ config QCOM_Q6V5_PIL | |||
114 | Say y here to support the Qualcomm Peripherial Image Loader for the | 117 | Say y here to support the Qualcomm Peripherial Image Loader for the |
115 | Hexagon V5 based remote processors. | 118 | Hexagon V5 based remote processors. |
116 | 119 | ||
120 | config QCOM_SYSMON | ||
121 | tristate "Qualcomm sysmon driver" | ||
122 | depends on RPMSG | ||
123 | depends on ARCH_QCOM | ||
124 | depends on NET | ||
125 | select QCOM_QMI_HELPERS | ||
126 | help | ||
127 | The sysmon driver implements a sysmon QMI client and a handler for | ||
128 | the sys_mon SMD and GLINK channel, which are used for graceful | ||
129 | shutdown, retrieving failure information and propagating information | ||
130 | about other subsystems being shut down. | ||
131 | |||
132 | Say y here if your system runs firmware on any other subsystems, e.g. | ||
133 | modem or DSP. | ||
134 | |||
117 | config QCOM_WCNSS_PIL | 135 | config QCOM_WCNSS_PIL |
118 | tristate "Qualcomm WCNSS Peripheral Image Loader" | 136 | tristate "Qualcomm WCNSS Peripheral Image Loader" |
119 | depends on OF && ARCH_QCOM | 137 | depends on OF && ARCH_QCOM |
120 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) | 138 | depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) |
121 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n | 139 | depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n |
122 | depends on QCOM_SMEM | 140 | depends on QCOM_SMEM |
141 | depends on QCOM_SYSMON || QCOM_SYSMON=n | ||
123 | select QCOM_MDT_LOADER | 142 | select QCOM_MDT_LOADER |
124 | select QCOM_RPROC_COMMON | 143 | select QCOM_RPROC_COMMON |
125 | select QCOM_SCM | 144 | select QCOM_SCM |
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 6e16450ce11f..02627ede8d4a 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile | |||
@@ -17,6 +17,7 @@ obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o | |||
17 | obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o | 17 | obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o |
18 | obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o | 18 | obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o |
19 | obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o | 19 | obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o |
20 | obj-$(CONFIG_QCOM_SYSMON) += qcom_sysmon.o | ||
20 | obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o | 21 | obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o |
21 | qcom_wcnss_pil-y += qcom_wcnss.o | 22 | qcom_wcnss_pil-y += qcom_wcnss.o |
22 | qcom_wcnss_pil-y += qcom_wcnss_iris.o | 23 | qcom_wcnss_pil-y += qcom_wcnss_iris.o |
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index 633268e9d550..54c07fd3f204 100644 --- a/drivers/remoteproc/imx_rproc.c +++ b/drivers/remoteproc/imx_rproc.c | |||
@@ -333,14 +333,14 @@ static int imx_rproc_probe(struct platform_device *pdev) | |||
333 | /* set some other name then imx */ | 333 | /* set some other name then imx */ |
334 | rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops, | 334 | rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops, |
335 | NULL, sizeof(*priv)); | 335 | NULL, sizeof(*priv)); |
336 | if (!rproc) { | 336 | if (!rproc) |
337 | ret = -ENOMEM; | 337 | return -ENOMEM; |
338 | goto err; | ||
339 | } | ||
340 | 338 | ||
341 | dcfg = of_device_get_match_data(dev); | 339 | dcfg = of_device_get_match_data(dev); |
342 | if (!dcfg) | 340 | if (!dcfg) { |
343 | return -EINVAL; | 341 | ret = -EINVAL; |
342 | goto err_put_rproc; | ||
343 | } | ||
344 | 344 | ||
345 | priv = rproc->priv; | 345 | priv = rproc->priv; |
346 | priv->rproc = rproc; | 346 | priv->rproc = rproc; |
@@ -359,8 +359,8 @@ static int imx_rproc_probe(struct platform_device *pdev) | |||
359 | priv->clk = devm_clk_get(dev, NULL); | 359 | priv->clk = devm_clk_get(dev, NULL); |
360 | if (IS_ERR(priv->clk)) { | 360 | if (IS_ERR(priv->clk)) { |
361 | dev_err(dev, "Failed to get clock\n"); | 361 | dev_err(dev, "Failed to get clock\n"); |
362 | rproc_free(rproc); | 362 | ret = PTR_ERR(priv->clk); |
363 | return PTR_ERR(priv->clk); | 363 | goto err_put_rproc; |
364 | } | 364 | } |
365 | 365 | ||
366 | /* | 366 | /* |
@@ -370,8 +370,7 @@ static int imx_rproc_probe(struct platform_device *pdev) | |||
370 | ret = clk_prepare_enable(priv->clk); | 370 | ret = clk_prepare_enable(priv->clk); |
371 | if (ret) { | 371 | if (ret) { |
372 | dev_err(&rproc->dev, "Failed to enable clock\n"); | 372 | dev_err(&rproc->dev, "Failed to enable clock\n"); |
373 | rproc_free(rproc); | 373 | goto err_put_rproc; |
374 | return ret; | ||
375 | } | 374 | } |
376 | 375 | ||
377 | ret = rproc_add(rproc); | 376 | ret = rproc_add(rproc); |
@@ -380,13 +379,13 @@ static int imx_rproc_probe(struct platform_device *pdev) | |||
380 | goto err_put_clk; | 379 | goto err_put_clk; |
381 | } | 380 | } |
382 | 381 | ||
383 | return ret; | 382 | return 0; |
384 | 383 | ||
385 | err_put_clk: | 384 | err_put_clk: |
386 | clk_disable_unprepare(priv->clk); | 385 | clk_disable_unprepare(priv->clk); |
387 | err_put_rproc: | 386 | err_put_rproc: |
388 | rproc_free(rproc); | 387 | rproc_free(rproc); |
389 | err: | 388 | |
390 | return ret; | 389 | return ret; |
391 | } | 390 | } |
392 | 391 | ||
diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c index 373c167892d7..89a86ce07f99 100644 --- a/drivers/remoteproc/qcom_adsp_pil.c +++ b/drivers/remoteproc/qcom_adsp_pil.c | |||
@@ -38,7 +38,10 @@ struct adsp_data { | |||
38 | const char *firmware_name; | 38 | const char *firmware_name; |
39 | int pas_id; | 39 | int pas_id; |
40 | bool has_aggre2_clk; | 40 | bool has_aggre2_clk; |
41 | |||
41 | const char *ssr_name; | 42 | const char *ssr_name; |
43 | const char *sysmon_name; | ||
44 | int ssctl_id; | ||
42 | }; | 45 | }; |
43 | 46 | ||
44 | struct qcom_adsp { | 47 | struct qcom_adsp { |
@@ -75,6 +78,7 @@ struct qcom_adsp { | |||
75 | struct qcom_rproc_glink glink_subdev; | 78 | struct qcom_rproc_glink glink_subdev; |
76 | struct qcom_rproc_subdev smd_subdev; | 79 | struct qcom_rproc_subdev smd_subdev; |
77 | struct qcom_rproc_ssr ssr_subdev; | 80 | struct qcom_rproc_ssr ssr_subdev; |
81 | struct qcom_sysmon *sysmon; | ||
78 | }; | 82 | }; |
79 | 83 | ||
80 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) | 84 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) |
@@ -82,7 +86,9 @@ static int adsp_load(struct rproc *rproc, const struct firmware *fw) | |||
82 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | 86 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; |
83 | 87 | ||
84 | return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id, | 88 | return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id, |
85 | adsp->mem_region, adsp->mem_phys, adsp->mem_size); | 89 | adsp->mem_region, adsp->mem_phys, adsp->mem_size, |
90 | &adsp->mem_reloc); | ||
91 | |||
86 | } | 92 | } |
87 | 93 | ||
88 | static int adsp_start(struct rproc *rproc) | 94 | static int adsp_start(struct rproc *rproc) |
@@ -177,6 +183,7 @@ static const struct rproc_ops adsp_ops = { | |||
177 | .start = adsp_start, | 183 | .start = adsp_start, |
178 | .stop = adsp_stop, | 184 | .stop = adsp_stop, |
179 | .da_to_va = adsp_da_to_va, | 185 | .da_to_va = adsp_da_to_va, |
186 | .parse_fw = qcom_register_dump_segments, | ||
180 | .load = adsp_load, | 187 | .load = adsp_load, |
181 | }; | 188 | }; |
182 | 189 | ||
@@ -201,9 +208,6 @@ static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) | |||
201 | 208 | ||
202 | rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); | 209 | rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); |
203 | 210 | ||
204 | if (!IS_ERR(msg)) | ||
205 | msg[0] = '\0'; | ||
206 | |||
207 | return IRQ_HANDLED; | 211 | return IRQ_HANDLED; |
208 | } | 212 | } |
209 | 213 | ||
@@ -398,6 +402,9 @@ static int adsp_probe(struct platform_device *pdev) | |||
398 | qcom_add_glink_subdev(rproc, &adsp->glink_subdev); | 402 | qcom_add_glink_subdev(rproc, &adsp->glink_subdev); |
399 | qcom_add_smd_subdev(rproc, &adsp->smd_subdev); | 403 | qcom_add_smd_subdev(rproc, &adsp->smd_subdev); |
400 | qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name); | 404 | qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name); |
405 | adsp->sysmon = qcom_add_sysmon_subdev(rproc, | ||
406 | desc->sysmon_name, | ||
407 | desc->ssctl_id); | ||
401 | 408 | ||
402 | ret = rproc_add(rproc); | 409 | ret = rproc_add(rproc); |
403 | if (ret) | 410 | if (ret) |
@@ -419,6 +426,7 @@ static int adsp_remove(struct platform_device *pdev) | |||
419 | rproc_del(adsp->rproc); | 426 | rproc_del(adsp->rproc); |
420 | 427 | ||
421 | qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); | 428 | qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); |
429 | qcom_remove_sysmon_subdev(adsp->sysmon); | ||
422 | qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev); | 430 | qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev); |
423 | qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev); | 431 | qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev); |
424 | rproc_free(adsp->rproc); | 432 | rproc_free(adsp->rproc); |
@@ -432,6 +440,8 @@ static const struct adsp_data adsp_resource_init = { | |||
432 | .pas_id = 1, | 440 | .pas_id = 1, |
433 | .has_aggre2_clk = false, | 441 | .has_aggre2_clk = false, |
434 | .ssr_name = "lpass", | 442 | .ssr_name = "lpass", |
443 | .sysmon_name = "adsp", | ||
444 | .ssctl_id = 0x14, | ||
435 | }; | 445 | }; |
436 | 446 | ||
437 | static const struct adsp_data slpi_resource_init = { | 447 | static const struct adsp_data slpi_resource_init = { |
@@ -440,6 +450,8 @@ static const struct adsp_data slpi_resource_init = { | |||
440 | .pas_id = 12, | 450 | .pas_id = 12, |
441 | .has_aggre2_clk = true, | 451 | .has_aggre2_clk = true, |
442 | .ssr_name = "dsps", | 452 | .ssr_name = "dsps", |
453 | .sysmon_name = "slpi", | ||
454 | .ssctl_id = 0x16, | ||
443 | }; | 455 | }; |
444 | 456 | ||
445 | static const struct of_device_id adsp_of_match[] = { | 457 | static const struct of_device_id adsp_of_match[] = { |
diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c index 00602499713f..acfc99f82fb8 100644 --- a/drivers/remoteproc/qcom_common.c +++ b/drivers/remoteproc/qcom_common.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/remoteproc.h> | 22 | #include <linux/remoteproc.h> |
23 | #include <linux/rpmsg/qcom_glink.h> | 23 | #include <linux/rpmsg/qcom_glink.h> |
24 | #include <linux/rpmsg/qcom_smd.h> | 24 | #include <linux/rpmsg/qcom_smd.h> |
25 | #include <linux/soc/qcom/mdt_loader.h> | ||
25 | 26 | ||
26 | #include "remoteproc_internal.h" | 27 | #include "remoteproc_internal.h" |
27 | #include "qcom_common.h" | 28 | #include "qcom_common.h" |
@@ -41,7 +42,7 @@ static int glink_subdev_probe(struct rproc_subdev *subdev) | |||
41 | return PTR_ERR_OR_ZERO(glink->edge); | 42 | return PTR_ERR_OR_ZERO(glink->edge); |
42 | } | 43 | } |
43 | 44 | ||
44 | static void glink_subdev_remove(struct rproc_subdev *subdev) | 45 | static void glink_subdev_remove(struct rproc_subdev *subdev, bool crashed) |
45 | { | 46 | { |
46 | struct qcom_rproc_glink *glink = to_glink_subdev(subdev); | 47 | struct qcom_rproc_glink *glink = to_glink_subdev(subdev); |
47 | 48 | ||
@@ -74,11 +75,57 @@ EXPORT_SYMBOL_GPL(qcom_add_glink_subdev); | |||
74 | */ | 75 | */ |
75 | void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink) | 76 | void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink) |
76 | { | 77 | { |
78 | if (!glink->node) | ||
79 | return; | ||
80 | |||
77 | rproc_remove_subdev(rproc, &glink->subdev); | 81 | rproc_remove_subdev(rproc, &glink->subdev); |
78 | of_node_put(glink->node); | 82 | of_node_put(glink->node); |
79 | } | 83 | } |
80 | EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev); | 84 | EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev); |
81 | 85 | ||
86 | /** | ||
87 | * qcom_register_dump_segments() - register segments for coredump | ||
88 | * @rproc: remoteproc handle | ||
89 | * @fw: firmware header | ||
90 | * | ||
91 | * Register all segments of the ELF in the remoteproc coredump segment list | ||
92 | * | ||
93 | * Return: 0 on success, negative errno on failure. | ||
94 | */ | ||
95 | int qcom_register_dump_segments(struct rproc *rproc, | ||
96 | const struct firmware *fw) | ||
97 | { | ||
98 | const struct elf32_phdr *phdrs; | ||
99 | const struct elf32_phdr *phdr; | ||
100 | const struct elf32_hdr *ehdr; | ||
101 | int ret; | ||
102 | int i; | ||
103 | |||
104 | ehdr = (struct elf32_hdr *)fw->data; | ||
105 | phdrs = (struct elf32_phdr *)(ehdr + 1); | ||
106 | |||
107 | for (i = 0; i < ehdr->e_phnum; i++) { | ||
108 | phdr = &phdrs[i]; | ||
109 | |||
110 | if (phdr->p_type != PT_LOAD) | ||
111 | continue; | ||
112 | |||
113 | if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) | ||
114 | continue; | ||
115 | |||
116 | if (!phdr->p_memsz) | ||
117 | continue; | ||
118 | |||
119 | ret = rproc_coredump_add_segment(rproc, phdr->p_paddr, | ||
120 | phdr->p_memsz); | ||
121 | if (ret) | ||
122 | return ret; | ||
123 | } | ||
124 | |||
125 | return 0; | ||
126 | } | ||
127 | EXPORT_SYMBOL_GPL(qcom_register_dump_segments); | ||
128 | |||
82 | static int smd_subdev_probe(struct rproc_subdev *subdev) | 129 | static int smd_subdev_probe(struct rproc_subdev *subdev) |
83 | { | 130 | { |
84 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); | 131 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); |
@@ -88,7 +135,7 @@ static int smd_subdev_probe(struct rproc_subdev *subdev) | |||
88 | return PTR_ERR_OR_ZERO(smd->edge); | 135 | return PTR_ERR_OR_ZERO(smd->edge); |
89 | } | 136 | } |
90 | 137 | ||
91 | static void smd_subdev_remove(struct rproc_subdev *subdev) | 138 | static void smd_subdev_remove(struct rproc_subdev *subdev, bool crashed) |
92 | { | 139 | { |
93 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); | 140 | struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); |
94 | 141 | ||
@@ -121,6 +168,9 @@ EXPORT_SYMBOL_GPL(qcom_add_smd_subdev); | |||
121 | */ | 168 | */ |
122 | void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) | 169 | void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) |
123 | { | 170 | { |
171 | if (!smd->node) | ||
172 | return; | ||
173 | |||
124 | rproc_remove_subdev(rproc, &smd->subdev); | 174 | rproc_remove_subdev(rproc, &smd->subdev); |
125 | of_node_put(smd->node); | 175 | of_node_put(smd->node); |
126 | } | 176 | } |
@@ -157,7 +207,7 @@ static int ssr_notify_start(struct rproc_subdev *subdev) | |||
157 | return 0; | 207 | return 0; |
158 | } | 208 | } |
159 | 209 | ||
160 | static void ssr_notify_stop(struct rproc_subdev *subdev) | 210 | static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed) |
161 | { | 211 | { |
162 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); | 212 | struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); |
163 | 213 | ||
diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h index 728be9834d8b..58de71e4781c 100644 --- a/drivers/remoteproc/qcom_common.h +++ b/drivers/remoteproc/qcom_common.h | |||
@@ -4,6 +4,9 @@ | |||
4 | 4 | ||
5 | #include <linux/remoteproc.h> | 5 | #include <linux/remoteproc.h> |
6 | #include "remoteproc_internal.h" | 6 | #include "remoteproc_internal.h" |
7 | #include <linux/soc/qcom/qmi.h> | ||
8 | |||
9 | struct qcom_sysmon; | ||
7 | 10 | ||
8 | struct qcom_rproc_glink { | 11 | struct qcom_rproc_glink { |
9 | struct rproc_subdev subdev; | 12 | struct rproc_subdev subdev; |
@@ -30,6 +33,8 @@ struct qcom_rproc_ssr { | |||
30 | void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); | 33 | void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); |
31 | void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); | 34 | void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); |
32 | 35 | ||
36 | int qcom_register_dump_segments(struct rproc *rproc, const struct firmware *fw); | ||
37 | |||
33 | void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); | 38 | void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); |
34 | void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); | 39 | void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); |
35 | 40 | ||
@@ -37,4 +42,22 @@ void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr, | |||
37 | const char *ssr_name); | 42 | const char *ssr_name); |
38 | void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr); | 43 | void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr); |
39 | 44 | ||
45 | #if IS_ENABLED(CONFIG_QCOM_SYSMON) | ||
46 | struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, | ||
47 | const char *name, | ||
48 | int ssctl_instance); | ||
49 | void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon); | ||
50 | #else | ||
51 | static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, | ||
52 | const char *name, | ||
53 | int ssctl_instance) | ||
54 | { | ||
55 | return NULL; | ||
56 | } | ||
57 | |||
58 | static inline void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon) | ||
59 | { | ||
60 | } | ||
61 | #endif | ||
62 | |||
40 | #endif | 63 | #endif |
diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index b4e5e725848d..8e70a627e0bb 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c | |||
@@ -168,6 +168,7 @@ struct q6v5 { | |||
168 | 168 | ||
169 | struct qcom_rproc_subdev smd_subdev; | 169 | struct qcom_rproc_subdev smd_subdev; |
170 | struct qcom_rproc_ssr ssr_subdev; | 170 | struct qcom_rproc_ssr ssr_subdev; |
171 | struct qcom_sysmon *sysmon; | ||
171 | bool need_mem_protection; | 172 | bool need_mem_protection; |
172 | int mpss_perm; | 173 | int mpss_perm; |
173 | int mba_perm; | 174 | int mba_perm; |
@@ -939,9 +940,6 @@ static irqreturn_t q6v5_wdog_interrupt(int irq, void *dev) | |||
939 | 940 | ||
940 | rproc_report_crash(qproc->rproc, RPROC_WATCHDOG); | 941 | rproc_report_crash(qproc->rproc, RPROC_WATCHDOG); |
941 | 942 | ||
942 | if (!IS_ERR(msg)) | ||
943 | msg[0] = '\0'; | ||
944 | |||
945 | return IRQ_HANDLED; | 943 | return IRQ_HANDLED; |
946 | } | 944 | } |
947 | 945 | ||
@@ -959,9 +957,6 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *dev) | |||
959 | 957 | ||
960 | rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR); | 958 | rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR); |
961 | 959 | ||
962 | if (!IS_ERR(msg)) | ||
963 | msg[0] = '\0'; | ||
964 | |||
965 | return IRQ_HANDLED; | 960 | return IRQ_HANDLED; |
966 | } | 961 | } |
967 | 962 | ||
@@ -1215,6 +1210,7 @@ static int q6v5_probe(struct platform_device *pdev) | |||
1215 | qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); | 1210 | qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); |
1216 | qcom_add_smd_subdev(rproc, &qproc->smd_subdev); | 1211 | qcom_add_smd_subdev(rproc, &qproc->smd_subdev); |
1217 | qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); | 1212 | qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); |
1213 | qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12); | ||
1218 | 1214 | ||
1219 | ret = rproc_add(rproc); | 1215 | ret = rproc_add(rproc); |
1220 | if (ret) | 1216 | if (ret) |
@@ -1234,6 +1230,7 @@ static int q6v5_remove(struct platform_device *pdev) | |||
1234 | 1230 | ||
1235 | rproc_del(qproc->rproc); | 1231 | rproc_del(qproc->rproc); |
1236 | 1232 | ||
1233 | qcom_remove_sysmon_subdev(qproc->sysmon); | ||
1237 | qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); | 1234 | qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); |
1238 | qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); | 1235 | qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); |
1239 | rproc_free(qproc->rproc); | 1236 | rproc_free(qproc->rproc); |
diff --git a/drivers/remoteproc/qcom_sysmon.c b/drivers/remoteproc/qcom_sysmon.c new file mode 100644 index 000000000000..f085545d7da5 --- /dev/null +++ b/drivers/remoteproc/qcom_sysmon.c | |||
@@ -0,0 +1,579 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright (c) 2017, Linaro Ltd. | ||
4 | */ | ||
5 | #include <linux/firmware.h> | ||
6 | #include <linux/module.h> | ||
7 | #include <linux/notifier.h> | ||
8 | #include <linux/slab.h> | ||
9 | #include <linux/io.h> | ||
10 | #include <linux/notifier.h> | ||
11 | #include <linux/of_platform.h> | ||
12 | #include <linux/platform_device.h> | ||
13 | #include <linux/remoteproc/qcom_rproc.h> | ||
14 | #include <linux/rpmsg.h> | ||
15 | |||
16 | #include "qcom_common.h" | ||
17 | |||
18 | static BLOCKING_NOTIFIER_HEAD(sysmon_notifiers); | ||
19 | |||
20 | struct qcom_sysmon { | ||
21 | struct rproc_subdev subdev; | ||
22 | struct rproc *rproc; | ||
23 | |||
24 | struct list_head node; | ||
25 | |||
26 | const char *name; | ||
27 | |||
28 | int ssctl_version; | ||
29 | int ssctl_instance; | ||
30 | |||
31 | struct notifier_block nb; | ||
32 | |||
33 | struct device *dev; | ||
34 | |||
35 | struct rpmsg_endpoint *ept; | ||
36 | struct completion comp; | ||
37 | struct mutex lock; | ||
38 | |||
39 | bool ssr_ack; | ||
40 | |||
41 | struct qmi_handle qmi; | ||
42 | struct sockaddr_qrtr ssctl; | ||
43 | }; | ||
44 | |||
45 | static DEFINE_MUTEX(sysmon_lock); | ||
46 | static LIST_HEAD(sysmon_list); | ||
47 | |||
48 | /** | ||
49 | * sysmon_send_event() - send notification of other remote's SSR event | ||
50 | * @sysmon: sysmon context | ||
51 | * @name: other remote's name | ||
52 | */ | ||
53 | static void sysmon_send_event(struct qcom_sysmon *sysmon, const char *name) | ||
54 | { | ||
55 | char req[50]; | ||
56 | int len; | ||
57 | int ret; | ||
58 | |||
59 | len = snprintf(req, sizeof(req), "ssr:%s:before_shutdown", name); | ||
60 | if (len >= sizeof(req)) | ||
61 | return; | ||
62 | |||
63 | mutex_lock(&sysmon->lock); | ||
64 | reinit_completion(&sysmon->comp); | ||
65 | sysmon->ssr_ack = false; | ||
66 | |||
67 | ret = rpmsg_send(sysmon->ept, req, len); | ||
68 | if (ret < 0) { | ||
69 | dev_err(sysmon->dev, "failed to send sysmon event\n"); | ||
70 | goto out_unlock; | ||
71 | } | ||
72 | |||
73 | ret = wait_for_completion_timeout(&sysmon->comp, | ||
74 | msecs_to_jiffies(5000)); | ||
75 | if (!ret) { | ||
76 | dev_err(sysmon->dev, "timeout waiting for sysmon ack\n"); | ||
77 | goto out_unlock; | ||
78 | } | ||
79 | |||
80 | if (!sysmon->ssr_ack) | ||
81 | dev_err(sysmon->dev, "unexpected response to sysmon event\n"); | ||
82 | |||
83 | out_unlock: | ||
84 | mutex_unlock(&sysmon->lock); | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * sysmon_request_shutdown() - request graceful shutdown of remote | ||
89 | * @sysmon: sysmon context | ||
90 | */ | ||
91 | static void sysmon_request_shutdown(struct qcom_sysmon *sysmon) | ||
92 | { | ||
93 | char *req = "ssr:shutdown"; | ||
94 | int ret; | ||
95 | |||
96 | mutex_lock(&sysmon->lock); | ||
97 | reinit_completion(&sysmon->comp); | ||
98 | sysmon->ssr_ack = false; | ||
99 | |||
100 | ret = rpmsg_send(sysmon->ept, req, strlen(req) + 1); | ||
101 | if (ret < 0) { | ||
102 | dev_err(sysmon->dev, "send sysmon shutdown request failed\n"); | ||
103 | goto out_unlock; | ||
104 | } | ||
105 | |||
106 | ret = wait_for_completion_timeout(&sysmon->comp, | ||
107 | msecs_to_jiffies(5000)); | ||
108 | if (!ret) { | ||
109 | dev_err(sysmon->dev, "timeout waiting for sysmon ack\n"); | ||
110 | goto out_unlock; | ||
111 | } | ||
112 | |||
113 | if (!sysmon->ssr_ack) | ||
114 | dev_err(sysmon->dev, | ||
115 | "unexpected response to sysmon shutdown request\n"); | ||
116 | |||
117 | out_unlock: | ||
118 | mutex_unlock(&sysmon->lock); | ||
119 | } | ||
120 | |||
121 | static int sysmon_callback(struct rpmsg_device *rpdev, void *data, int count, | ||
122 | void *priv, u32 addr) | ||
123 | { | ||
124 | struct qcom_sysmon *sysmon = priv; | ||
125 | const char *ssr_ack = "ssr:ack"; | ||
126 | const int ssr_ack_len = strlen(ssr_ack) + 1; | ||
127 | |||
128 | if (!sysmon) | ||
129 | return -EINVAL; | ||
130 | |||
131 | if (count >= ssr_ack_len && !memcmp(data, ssr_ack, ssr_ack_len)) | ||
132 | sysmon->ssr_ack = true; | ||
133 | |||
134 | complete(&sysmon->comp); | ||
135 | |||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | #define SSCTL_SHUTDOWN_REQ 0x21 | ||
140 | #define SSCTL_SUBSYS_EVENT_REQ 0x23 | ||
141 | |||
142 | #define SSCTL_MAX_MSG_LEN 7 | ||
143 | |||
144 | #define SSCTL_SUBSYS_NAME_LENGTH 15 | ||
145 | |||
146 | enum { | ||
147 | SSCTL_SSR_EVENT_BEFORE_POWERUP, | ||
148 | SSCTL_SSR_EVENT_AFTER_POWERUP, | ||
149 | SSCTL_SSR_EVENT_BEFORE_SHUTDOWN, | ||
150 | SSCTL_SSR_EVENT_AFTER_SHUTDOWN, | ||
151 | }; | ||
152 | |||
153 | enum { | ||
154 | SSCTL_SSR_EVENT_FORCED, | ||
155 | SSCTL_SSR_EVENT_GRACEFUL, | ||
156 | }; | ||
157 | |||
158 | struct ssctl_shutdown_resp { | ||
159 | struct qmi_response_type_v01 resp; | ||
160 | }; | ||
161 | |||
162 | static struct qmi_elem_info ssctl_shutdown_resp_ei[] = { | ||
163 | { | ||
164 | .data_type = QMI_STRUCT, | ||
165 | .elem_len = 1, | ||
166 | .elem_size = sizeof(struct qmi_response_type_v01), | ||
167 | .array_type = NO_ARRAY, | ||
168 | .tlv_type = 0x02, | ||
169 | .offset = offsetof(struct ssctl_shutdown_resp, resp), | ||
170 | .ei_array = qmi_response_type_v01_ei, | ||
171 | }, | ||
172 | {} | ||
173 | }; | ||
174 | |||
175 | struct ssctl_subsys_event_req { | ||
176 | u8 subsys_name_len; | ||
177 | char subsys_name[SSCTL_SUBSYS_NAME_LENGTH]; | ||
178 | u32 event; | ||
179 | u8 evt_driven_valid; | ||
180 | u32 evt_driven; | ||
181 | }; | ||
182 | |||
183 | static struct qmi_elem_info ssctl_subsys_event_req_ei[] = { | ||
184 | { | ||
185 | .data_type = QMI_DATA_LEN, | ||
186 | .elem_len = 1, | ||
187 | .elem_size = sizeof(uint8_t), | ||
188 | .array_type = NO_ARRAY, | ||
189 | .tlv_type = 0x01, | ||
190 | .offset = offsetof(struct ssctl_subsys_event_req, | ||
191 | subsys_name_len), | ||
192 | .ei_array = NULL, | ||
193 | }, | ||
194 | { | ||
195 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
196 | .elem_len = SSCTL_SUBSYS_NAME_LENGTH, | ||
197 | .elem_size = sizeof(char), | ||
198 | .array_type = VAR_LEN_ARRAY, | ||
199 | .tlv_type = 0x01, | ||
200 | .offset = offsetof(struct ssctl_subsys_event_req, | ||
201 | subsys_name), | ||
202 | .ei_array = NULL, | ||
203 | }, | ||
204 | { | ||
205 | .data_type = QMI_SIGNED_4_BYTE_ENUM, | ||
206 | .elem_len = 1, | ||
207 | .elem_size = sizeof(uint32_t), | ||
208 | .array_type = NO_ARRAY, | ||
209 | .tlv_type = 0x02, | ||
210 | .offset = offsetof(struct ssctl_subsys_event_req, | ||
211 | event), | ||
212 | .ei_array = NULL, | ||
213 | }, | ||
214 | { | ||
215 | .data_type = QMI_OPT_FLAG, | ||
216 | .elem_len = 1, | ||
217 | .elem_size = sizeof(uint8_t), | ||
218 | .array_type = NO_ARRAY, | ||
219 | .tlv_type = 0x10, | ||
220 | .offset = offsetof(struct ssctl_subsys_event_req, | ||
221 | evt_driven_valid), | ||
222 | .ei_array = NULL, | ||
223 | }, | ||
224 | { | ||
225 | .data_type = QMI_SIGNED_4_BYTE_ENUM, | ||
226 | .elem_len = 1, | ||
227 | .elem_size = sizeof(uint32_t), | ||
228 | .array_type = NO_ARRAY, | ||
229 | .tlv_type = 0x10, | ||
230 | .offset = offsetof(struct ssctl_subsys_event_req, | ||
231 | evt_driven), | ||
232 | .ei_array = NULL, | ||
233 | }, | ||
234 | {} | ||
235 | }; | ||
236 | |||
237 | struct ssctl_subsys_event_resp { | ||
238 | struct qmi_response_type_v01 resp; | ||
239 | }; | ||
240 | |||
241 | static struct qmi_elem_info ssctl_subsys_event_resp_ei[] = { | ||
242 | { | ||
243 | .data_type = QMI_STRUCT, | ||
244 | .elem_len = 1, | ||
245 | .elem_size = sizeof(struct qmi_response_type_v01), | ||
246 | .array_type = NO_ARRAY, | ||
247 | .tlv_type = 0x02, | ||
248 | .offset = offsetof(struct ssctl_subsys_event_resp, | ||
249 | resp), | ||
250 | .ei_array = qmi_response_type_v01_ei, | ||
251 | }, | ||
252 | {} | ||
253 | }; | ||
254 | |||
255 | /** | ||
256 | * ssctl_request_shutdown() - request shutdown via SSCTL QMI service | ||
257 | * @sysmon: sysmon context | ||
258 | */ | ||
259 | static void ssctl_request_shutdown(struct qcom_sysmon *sysmon) | ||
260 | { | ||
261 | struct ssctl_shutdown_resp resp; | ||
262 | struct qmi_txn txn; | ||
263 | int ret; | ||
264 | |||
265 | ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_shutdown_resp_ei, &resp); | ||
266 | if (ret < 0) { | ||
267 | dev_err(sysmon->dev, "failed to allocate QMI txn\n"); | ||
268 | return; | ||
269 | } | ||
270 | |||
271 | ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn, | ||
272 | SSCTL_SHUTDOWN_REQ, 0, NULL, NULL); | ||
273 | if (ret < 0) { | ||
274 | dev_err(sysmon->dev, "failed to send shutdown request\n"); | ||
275 | qmi_txn_cancel(&txn); | ||
276 | return; | ||
277 | } | ||
278 | |||
279 | ret = qmi_txn_wait(&txn, 5 * HZ); | ||
280 | if (ret < 0) | ||
281 | dev_err(sysmon->dev, "failed receiving QMI response\n"); | ||
282 | else if (resp.resp.result) | ||
283 | dev_err(sysmon->dev, "shutdown request failed\n"); | ||
284 | else | ||
285 | dev_dbg(sysmon->dev, "shutdown request completed\n"); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * ssctl_send_event() - send notification of other remote's SSR event | ||
290 | * @sysmon: sysmon context | ||
291 | * @name: other remote's name | ||
292 | */ | ||
293 | static void ssctl_send_event(struct qcom_sysmon *sysmon, const char *name) | ||
294 | { | ||
295 | struct ssctl_subsys_event_resp resp; | ||
296 | struct ssctl_subsys_event_req req; | ||
297 | struct qmi_txn txn; | ||
298 | int ret; | ||
299 | |||
300 | memset(&resp, 0, sizeof(resp)); | ||
301 | ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_subsys_event_resp_ei, &resp); | ||
302 | if (ret < 0) { | ||
303 | dev_err(sysmon->dev, "failed to allocate QMI txn\n"); | ||
304 | return; | ||
305 | } | ||
306 | |||
307 | memset(&req, 0, sizeof(req)); | ||
308 | strlcpy(req.subsys_name, name, sizeof(req.subsys_name)); | ||
309 | req.subsys_name_len = strlen(req.subsys_name); | ||
310 | req.event = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN; | ||
311 | req.evt_driven_valid = true; | ||
312 | req.evt_driven = SSCTL_SSR_EVENT_FORCED; | ||
313 | |||
314 | ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn, | ||
315 | SSCTL_SUBSYS_EVENT_REQ, 40, | ||
316 | ssctl_subsys_event_req_ei, &req); | ||
317 | if (ret < 0) { | ||
318 | dev_err(sysmon->dev, "failed to send shutdown request\n"); | ||
319 | qmi_txn_cancel(&txn); | ||
320 | return; | ||
321 | } | ||
322 | |||
323 | ret = qmi_txn_wait(&txn, 5 * HZ); | ||
324 | if (ret < 0) | ||
325 | dev_err(sysmon->dev, "failed receiving QMI response\n"); | ||
326 | else if (resp.resp.result) | ||
327 | dev_err(sysmon->dev, "ssr event send failed\n"); | ||
328 | else | ||
329 | dev_dbg(sysmon->dev, "ssr event send completed\n"); | ||
330 | } | ||
331 | |||
332 | /** | ||
333 | * ssctl_new_server() - QMI callback indicating a new service | ||
334 | * @qmi: QMI handle | ||
335 | * @svc: service information | ||
336 | * | ||
337 | * Return: 0 if we're interested in this service, -EINVAL otherwise. | ||
338 | */ | ||
339 | static int ssctl_new_server(struct qmi_handle *qmi, struct qmi_service *svc) | ||
340 | { | ||
341 | struct qcom_sysmon *sysmon = container_of(qmi, struct qcom_sysmon, qmi); | ||
342 | |||
343 | switch (svc->version) { | ||
344 | case 1: | ||
345 | if (svc->instance != 0) | ||
346 | return -EINVAL; | ||
347 | if (strcmp(sysmon->name, "modem")) | ||
348 | return -EINVAL; | ||
349 | break; | ||
350 | case 2: | ||
351 | if (svc->instance != sysmon->ssctl_instance) | ||
352 | return -EINVAL; | ||
353 | break; | ||
354 | default: | ||
355 | return -EINVAL; | ||
356 | }; | ||
357 | |||
358 | sysmon->ssctl_version = svc->version; | ||
359 | |||
360 | sysmon->ssctl.sq_family = AF_QIPCRTR; | ||
361 | sysmon->ssctl.sq_node = svc->node; | ||
362 | sysmon->ssctl.sq_port = svc->port; | ||
363 | |||
364 | svc->priv = sysmon; | ||
365 | |||
366 | return 0; | ||
367 | } | ||
368 | |||
369 | /** | ||
370 | * ssctl_del_server() - QMI callback indicating that @svc is removed | ||
371 | * @qmi: QMI handle | ||
372 | * @svc: service information | ||
373 | */ | ||
374 | static void ssctl_del_server(struct qmi_handle *qmi, struct qmi_service *svc) | ||
375 | { | ||
376 | struct qcom_sysmon *sysmon = svc->priv; | ||
377 | |||
378 | sysmon->ssctl_version = 0; | ||
379 | } | ||
380 | |||
381 | static const struct qmi_ops ssctl_ops = { | ||
382 | .new_server = ssctl_new_server, | ||
383 | .del_server = ssctl_del_server, | ||
384 | }; | ||
385 | |||
386 | static int sysmon_start(struct rproc_subdev *subdev) | ||
387 | { | ||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | static void sysmon_stop(struct rproc_subdev *subdev, bool crashed) | ||
392 | { | ||
393 | struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon, subdev); | ||
394 | |||
395 | blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)sysmon->name); | ||
396 | |||
397 | /* Don't request graceful shutdown if we've crashed */ | ||
398 | if (crashed) | ||
399 | return; | ||
400 | |||
401 | if (sysmon->ssctl_version) | ||
402 | ssctl_request_shutdown(sysmon); | ||
403 | else if (sysmon->ept) | ||
404 | sysmon_request_shutdown(sysmon); | ||
405 | } | ||
406 | |||
407 | /** | ||
408 | * sysmon_notify() - notify sysmon target of another's SSR | ||
409 | * @nb: notifier_block associated with sysmon instance | ||
410 | * @event: unused | ||
411 | * @data: SSR identifier of the remote that is going down | ||
412 | */ | ||
413 | static int sysmon_notify(struct notifier_block *nb, unsigned long event, | ||
414 | void *data) | ||
415 | { | ||
416 | struct qcom_sysmon *sysmon = container_of(nb, struct qcom_sysmon, nb); | ||
417 | struct rproc *rproc = sysmon->rproc; | ||
418 | const char *ssr_name = data; | ||
419 | |||
420 | /* Skip non-running rprocs and the originating instance */ | ||
421 | if (rproc->state != RPROC_RUNNING || !strcmp(data, sysmon->name)) { | ||
422 | dev_dbg(sysmon->dev, "not notifying %s\n", sysmon->name); | ||
423 | return NOTIFY_DONE; | ||
424 | } | ||
425 | |||
426 | /* Only SSCTL version 2 supports SSR events */ | ||
427 | if (sysmon->ssctl_version == 2) | ||
428 | ssctl_send_event(sysmon, ssr_name); | ||
429 | else if (sysmon->ept) | ||
430 | sysmon_send_event(sysmon, ssr_name); | ||
431 | |||
432 | return NOTIFY_DONE; | ||
433 | } | ||
434 | |||
435 | /** | ||
436 | * qcom_add_sysmon_subdev() - create a sysmon subdev for the given remoteproc | ||
437 | * @rproc: rproc context to associate the subdev with | ||
438 | * @name: name of this subdev, to use in SSR | ||
439 | * @ssctl_instance: instance id of the ssctl QMI service | ||
440 | * | ||
441 | * Return: A new qcom_sysmon object, or NULL on failure | ||
442 | */ | ||
443 | struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, | ||
444 | const char *name, | ||
445 | int ssctl_instance) | ||
446 | { | ||
447 | struct qcom_sysmon *sysmon; | ||
448 | int ret; | ||
449 | |||
450 | sysmon = kzalloc(sizeof(*sysmon), GFP_KERNEL); | ||
451 | if (!sysmon) | ||
452 | return NULL; | ||
453 | |||
454 | sysmon->dev = rproc->dev.parent; | ||
455 | sysmon->rproc = rproc; | ||
456 | |||
457 | sysmon->name = name; | ||
458 | sysmon->ssctl_instance = ssctl_instance; | ||
459 | |||
460 | init_completion(&sysmon->comp); | ||
461 | mutex_init(&sysmon->lock); | ||
462 | |||
463 | ret = qmi_handle_init(&sysmon->qmi, SSCTL_MAX_MSG_LEN, &ssctl_ops, NULL); | ||
464 | if (ret < 0) { | ||
465 | dev_err(sysmon->dev, "failed to initialize qmi handle\n"); | ||
466 | kfree(sysmon); | ||
467 | return NULL; | ||
468 | } | ||
469 | |||
470 | qmi_add_lookup(&sysmon->qmi, 43, 0, 0); | ||
471 | |||
472 | rproc_add_subdev(rproc, &sysmon->subdev, sysmon_start, sysmon_stop); | ||
473 | |||
474 | sysmon->nb.notifier_call = sysmon_notify; | ||
475 | blocking_notifier_chain_register(&sysmon_notifiers, &sysmon->nb); | ||
476 | |||
477 | mutex_lock(&sysmon_lock); | ||
478 | list_add(&sysmon->node, &sysmon_list); | ||
479 | mutex_unlock(&sysmon_lock); | ||
480 | |||
481 | return sysmon; | ||
482 | } | ||
483 | EXPORT_SYMBOL_GPL(qcom_add_sysmon_subdev); | ||
484 | |||
485 | /** | ||
486 | * qcom_remove_sysmon_subdev() - release a qcom_sysmon | ||
487 | * @sysmon: sysmon context, as retrieved by qcom_add_sysmon_subdev() | ||
488 | */ | ||
489 | void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon) | ||
490 | { | ||
491 | if (!sysmon) | ||
492 | return; | ||
493 | |||
494 | mutex_lock(&sysmon_lock); | ||
495 | list_del(&sysmon->node); | ||
496 | mutex_unlock(&sysmon_lock); | ||
497 | |||
498 | blocking_notifier_chain_unregister(&sysmon_notifiers, &sysmon->nb); | ||
499 | |||
500 | rproc_remove_subdev(sysmon->rproc, &sysmon->subdev); | ||
501 | |||
502 | qmi_handle_release(&sysmon->qmi); | ||
503 | |||
504 | kfree(sysmon); | ||
505 | } | ||
506 | EXPORT_SYMBOL_GPL(qcom_remove_sysmon_subdev); | ||
507 | |||
508 | /** | ||
509 | * sysmon_probe() - probe sys_mon channel | ||
510 | * @rpdev: rpmsg device handle | ||
511 | * | ||
512 | * Find the sysmon context associated with the ancestor remoteproc and assign | ||
513 | * this rpmsg device with said sysmon context. | ||
514 | * | ||
515 | * Return: 0 on success, negative errno on failure. | ||
516 | */ | ||
517 | static int sysmon_probe(struct rpmsg_device *rpdev) | ||
518 | { | ||
519 | struct qcom_sysmon *sysmon; | ||
520 | struct rproc *rproc; | ||
521 | |||
522 | rproc = rproc_get_by_child(&rpdev->dev); | ||
523 | if (!rproc) { | ||
524 | dev_err(&rpdev->dev, "sysmon device not child of rproc\n"); | ||
525 | return -EINVAL; | ||
526 | } | ||
527 | |||
528 | mutex_lock(&sysmon_lock); | ||
529 | list_for_each_entry(sysmon, &sysmon_list, node) { | ||
530 | if (sysmon->rproc == rproc) | ||
531 | goto found; | ||
532 | } | ||
533 | mutex_unlock(&sysmon_lock); | ||
534 | |||
535 | dev_err(&rpdev->dev, "no sysmon associated with parent rproc\n"); | ||
536 | |||
537 | return -EINVAL; | ||
538 | |||
539 | found: | ||
540 | mutex_unlock(&sysmon_lock); | ||
541 | |||
542 | rpdev->ept->priv = sysmon; | ||
543 | sysmon->ept = rpdev->ept; | ||
544 | |||
545 | return 0; | ||
546 | } | ||
547 | |||
548 | /** | ||
549 | * sysmon_remove() - sys_mon channel remove handler | ||
550 | * @rpdev: rpmsg device handle | ||
551 | * | ||
552 | * Disassociate the rpmsg device with the sysmon instance. | ||
553 | */ | ||
554 | static void sysmon_remove(struct rpmsg_device *rpdev) | ||
555 | { | ||
556 | struct qcom_sysmon *sysmon = rpdev->ept->priv; | ||
557 | |||
558 | sysmon->ept = NULL; | ||
559 | } | ||
560 | |||
561 | static const struct rpmsg_device_id sysmon_match[] = { | ||
562 | { "sys_mon" }, | ||
563 | {} | ||
564 | }; | ||
565 | |||
566 | static struct rpmsg_driver sysmon_driver = { | ||
567 | .probe = sysmon_probe, | ||
568 | .remove = sysmon_remove, | ||
569 | .callback = sysmon_callback, | ||
570 | .id_table = sysmon_match, | ||
571 | .drv = { | ||
572 | .name = "qcom_sysmon", | ||
573 | }, | ||
574 | }; | ||
575 | |||
576 | module_rpmsg_driver(sysmon_driver); | ||
577 | |||
578 | MODULE_DESCRIPTION("Qualcomm sysmon driver"); | ||
579 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index 3f0609236a76..b0e07e9f42d5 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c | |||
@@ -40,6 +40,7 @@ | |||
40 | #define WCNSS_CRASH_REASON_SMEM 422 | 40 | #define WCNSS_CRASH_REASON_SMEM 422 |
41 | #define WCNSS_FIRMWARE_NAME "wcnss.mdt" | 41 | #define WCNSS_FIRMWARE_NAME "wcnss.mdt" |
42 | #define WCNSS_PAS_ID 6 | 42 | #define WCNSS_PAS_ID 6 |
43 | #define WCNSS_SSCTL_ID 0x13 | ||
43 | 44 | ||
44 | #define WCNSS_SPARE_NVBIN_DLND BIT(25) | 45 | #define WCNSS_SPARE_NVBIN_DLND BIT(25) |
45 | 46 | ||
@@ -98,6 +99,7 @@ struct qcom_wcnss { | |||
98 | size_t mem_size; | 99 | size_t mem_size; |
99 | 100 | ||
100 | struct qcom_rproc_subdev smd_subdev; | 101 | struct qcom_rproc_subdev smd_subdev; |
102 | struct qcom_sysmon *sysmon; | ||
101 | }; | 103 | }; |
102 | 104 | ||
103 | static const struct wcnss_data riva_data = { | 105 | static const struct wcnss_data riva_data = { |
@@ -153,7 +155,8 @@ static int wcnss_load(struct rproc *rproc, const struct firmware *fw) | |||
153 | struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; | 155 | struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; |
154 | 156 | ||
155 | return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, | 157 | return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, |
156 | wcnss->mem_region, wcnss->mem_phys, wcnss->mem_size); | 158 | wcnss->mem_region, wcnss->mem_phys, |
159 | wcnss->mem_size, &wcnss->mem_reloc); | ||
157 | } | 160 | } |
158 | 161 | ||
159 | static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss) | 162 | static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss) |
@@ -308,6 +311,7 @@ static const struct rproc_ops wcnss_ops = { | |||
308 | .start = wcnss_start, | 311 | .start = wcnss_start, |
309 | .stop = wcnss_stop, | 312 | .stop = wcnss_stop, |
310 | .da_to_va = wcnss_da_to_va, | 313 | .da_to_va = wcnss_da_to_va, |
314 | .parse_fw = qcom_register_dump_segments, | ||
311 | .load = wcnss_load, | 315 | .load = wcnss_load, |
312 | }; | 316 | }; |
313 | 317 | ||
@@ -332,9 +336,6 @@ static irqreturn_t wcnss_fatal_interrupt(int irq, void *dev) | |||
332 | 336 | ||
333 | rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR); | 337 | rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR); |
334 | 338 | ||
335 | if (!IS_ERR(msg)) | ||
336 | msg[0] = '\0'; | ||
337 | |||
338 | return IRQ_HANDLED; | 339 | return IRQ_HANDLED; |
339 | } | 340 | } |
340 | 341 | ||
@@ -551,6 +552,7 @@ static int wcnss_probe(struct platform_device *pdev) | |||
551 | } | 552 | } |
552 | 553 | ||
553 | qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); | 554 | qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); |
555 | wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID); | ||
554 | 556 | ||
555 | ret = rproc_add(rproc); | 557 | ret = rproc_add(rproc); |
556 | if (ret) | 558 | if (ret) |
@@ -573,6 +575,7 @@ static int wcnss_remove(struct platform_device *pdev) | |||
573 | qcom_smem_state_put(wcnss->state); | 575 | qcom_smem_state_put(wcnss->state); |
574 | rproc_del(wcnss->rproc); | 576 | rproc_del(wcnss->rproc); |
575 | 577 | ||
578 | qcom_remove_sysmon_subdev(wcnss->sysmon); | ||
576 | qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); | 579 | qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); |
577 | rproc_free(wcnss->rproc); | 580 | rproc_free(wcnss->rproc); |
578 | 581 | ||
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 4170dfbd93bd..6d9c5832ce47 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c | |||
@@ -33,6 +33,7 @@ | |||
33 | #include <linux/firmware.h> | 33 | #include <linux/firmware.h> |
34 | #include <linux/string.h> | 34 | #include <linux/string.h> |
35 | #include <linux/debugfs.h> | 35 | #include <linux/debugfs.h> |
36 | #include <linux/devcoredump.h> | ||
36 | #include <linux/remoteproc.h> | 37 | #include <linux/remoteproc.h> |
37 | #include <linux/iommu.h> | 38 | #include <linux/iommu.h> |
38 | #include <linux/idr.h> | 39 | #include <linux/idr.h> |
@@ -307,7 +308,7 @@ static int rproc_vdev_do_probe(struct rproc_subdev *subdev) | |||
307 | return rproc_add_virtio_dev(rvdev, rvdev->id); | 308 | return rproc_add_virtio_dev(rvdev, rvdev->id); |
308 | } | 309 | } |
309 | 310 | ||
310 | static void rproc_vdev_do_remove(struct rproc_subdev *subdev) | 311 | static void rproc_vdev_do_remove(struct rproc_subdev *subdev, bool crashed) |
311 | { | 312 | { |
312 | struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); | 313 | struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); |
313 | 314 | ||
@@ -788,17 +789,31 @@ static int rproc_probe_subdevices(struct rproc *rproc) | |||
788 | 789 | ||
789 | unroll_registration: | 790 | unroll_registration: |
790 | list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node) | 791 | list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node) |
791 | subdev->remove(subdev); | 792 | subdev->remove(subdev, true); |
792 | 793 | ||
793 | return ret; | 794 | return ret; |
794 | } | 795 | } |
795 | 796 | ||
796 | static void rproc_remove_subdevices(struct rproc *rproc) | 797 | static void rproc_remove_subdevices(struct rproc *rproc, bool crashed) |
797 | { | 798 | { |
798 | struct rproc_subdev *subdev; | 799 | struct rproc_subdev *subdev; |
799 | 800 | ||
800 | list_for_each_entry_reverse(subdev, &rproc->subdevs, node) | 801 | list_for_each_entry_reverse(subdev, &rproc->subdevs, node) |
801 | subdev->remove(subdev); | 802 | subdev->remove(subdev, crashed); |
803 | } | ||
804 | |||
805 | /** | ||
806 | * rproc_coredump_cleanup() - clean up dump_segments list | ||
807 | * @rproc: the remote processor handle | ||
808 | */ | ||
809 | static void rproc_coredump_cleanup(struct rproc *rproc) | ||
810 | { | ||
811 | struct rproc_dump_segment *entry, *tmp; | ||
812 | |||
813 | list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) { | ||
814 | list_del(&entry->node); | ||
815 | kfree(entry); | ||
816 | } | ||
802 | } | 817 | } |
803 | 818 | ||
804 | /** | 819 | /** |
@@ -848,6 +863,8 @@ static void rproc_resource_cleanup(struct rproc *rproc) | |||
848 | /* clean up remote vdev entries */ | 863 | /* clean up remote vdev entries */ |
849 | list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) | 864 | list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) |
850 | kref_put(&rvdev->refcount, rproc_vdev_release); | 865 | kref_put(&rvdev->refcount, rproc_vdev_release); |
866 | |||
867 | rproc_coredump_cleanup(rproc); | ||
851 | } | 868 | } |
852 | 869 | ||
853 | static int rproc_start(struct rproc *rproc, const struct firmware *fw) | 870 | static int rproc_start(struct rproc *rproc, const struct firmware *fw) |
@@ -927,8 +944,8 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) | |||
927 | 944 | ||
928 | rproc->bootaddr = rproc_get_boot_addr(rproc, fw); | 945 | rproc->bootaddr = rproc_get_boot_addr(rproc, fw); |
929 | 946 | ||
930 | /* load resource table */ | 947 | /* Load resource table, core dump segment list etc from the firmware */ |
931 | ret = rproc_load_rsc_table(rproc, fw); | 948 | ret = rproc_parse_fw(rproc, fw); |
932 | if (ret) | 949 | if (ret) |
933 | goto disable_iommu; | 950 | goto disable_iommu; |
934 | 951 | ||
@@ -992,13 +1009,13 @@ static int rproc_trigger_auto_boot(struct rproc *rproc) | |||
992 | return ret; | 1009 | return ret; |
993 | } | 1010 | } |
994 | 1011 | ||
995 | static int rproc_stop(struct rproc *rproc) | 1012 | static int rproc_stop(struct rproc *rproc, bool crashed) |
996 | { | 1013 | { |
997 | struct device *dev = &rproc->dev; | 1014 | struct device *dev = &rproc->dev; |
998 | int ret; | 1015 | int ret; |
999 | 1016 | ||
1000 | /* remove any subdevices for the remote processor */ | 1017 | /* remove any subdevices for the remote processor */ |
1001 | rproc_remove_subdevices(rproc); | 1018 | rproc_remove_subdevices(rproc, crashed); |
1002 | 1019 | ||
1003 | /* the installed resource table is no longer accessible */ | 1020 | /* the installed resource table is no longer accessible */ |
1004 | rproc->table_ptr = rproc->cached_table; | 1021 | rproc->table_ptr = rproc->cached_table; |
@@ -1018,6 +1035,113 @@ static int rproc_stop(struct rproc *rproc) | |||
1018 | } | 1035 | } |
1019 | 1036 | ||
1020 | /** | 1037 | /** |
1038 | * rproc_coredump_add_segment() - add segment of device memory to coredump | ||
1039 | * @rproc: handle of a remote processor | ||
1040 | * @da: device address | ||
1041 | * @size: size of segment | ||
1042 | * | ||
1043 | * Add device memory to the list of segments to be included in a coredump for | ||
1044 | * the remoteproc. | ||
1045 | * | ||
1046 | * Return: 0 on success, negative errno on error. | ||
1047 | */ | ||
1048 | int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size) | ||
1049 | { | ||
1050 | struct rproc_dump_segment *segment; | ||
1051 | |||
1052 | segment = kzalloc(sizeof(*segment), GFP_KERNEL); | ||
1053 | if (!segment) | ||
1054 | return -ENOMEM; | ||
1055 | |||
1056 | segment->da = da; | ||
1057 | segment->size = size; | ||
1058 | |||
1059 | list_add_tail(&segment->node, &rproc->dump_segments); | ||
1060 | |||
1061 | return 0; | ||
1062 | } | ||
1063 | EXPORT_SYMBOL(rproc_coredump_add_segment); | ||
1064 | |||
1065 | /** | ||
1066 | * rproc_coredump() - perform coredump | ||
1067 | * @rproc: rproc handle | ||
1068 | * | ||
1069 | * This function will generate an ELF header for the registered segments | ||
1070 | * and create a devcoredump device associated with rproc. | ||
1071 | */ | ||
1072 | static void rproc_coredump(struct rproc *rproc) | ||
1073 | { | ||
1074 | struct rproc_dump_segment *segment; | ||
1075 | struct elf32_phdr *phdr; | ||
1076 | struct elf32_hdr *ehdr; | ||
1077 | size_t data_size; | ||
1078 | size_t offset; | ||
1079 | void *data; | ||
1080 | void *ptr; | ||
1081 | int phnum = 0; | ||
1082 | |||
1083 | if (list_empty(&rproc->dump_segments)) | ||
1084 | return; | ||
1085 | |||
1086 | data_size = sizeof(*ehdr); | ||
1087 | list_for_each_entry(segment, &rproc->dump_segments, node) { | ||
1088 | data_size += sizeof(*phdr) + segment->size; | ||
1089 | |||
1090 | phnum++; | ||
1091 | } | ||
1092 | |||
1093 | data = vmalloc(data_size); | ||
1094 | if (!data) | ||
1095 | return; | ||
1096 | |||
1097 | ehdr = data; | ||
1098 | |||
1099 | memset(ehdr, 0, sizeof(*ehdr)); | ||
1100 | memcpy(ehdr->e_ident, ELFMAG, SELFMAG); | ||
1101 | ehdr->e_ident[EI_CLASS] = ELFCLASS32; | ||
1102 | ehdr->e_ident[EI_DATA] = ELFDATA2LSB; | ||
1103 | ehdr->e_ident[EI_VERSION] = EV_CURRENT; | ||
1104 | ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE; | ||
1105 | ehdr->e_type = ET_CORE; | ||
1106 | ehdr->e_machine = EM_NONE; | ||
1107 | ehdr->e_version = EV_CURRENT; | ||
1108 | ehdr->e_entry = rproc->bootaddr; | ||
1109 | ehdr->e_phoff = sizeof(*ehdr); | ||
1110 | ehdr->e_ehsize = sizeof(*ehdr); | ||
1111 | ehdr->e_phentsize = sizeof(*phdr); | ||
1112 | ehdr->e_phnum = phnum; | ||
1113 | |||
1114 | phdr = data + ehdr->e_phoff; | ||
1115 | offset = ehdr->e_phoff + sizeof(*phdr) * ehdr->e_phnum; | ||
1116 | list_for_each_entry(segment, &rproc->dump_segments, node) { | ||
1117 | memset(phdr, 0, sizeof(*phdr)); | ||
1118 | phdr->p_type = PT_LOAD; | ||
1119 | phdr->p_offset = offset; | ||
1120 | phdr->p_vaddr = segment->da; | ||
1121 | phdr->p_paddr = segment->da; | ||
1122 | phdr->p_filesz = segment->size; | ||
1123 | phdr->p_memsz = segment->size; | ||
1124 | phdr->p_flags = PF_R | PF_W | PF_X; | ||
1125 | phdr->p_align = 0; | ||
1126 | |||
1127 | ptr = rproc_da_to_va(rproc, segment->da, segment->size); | ||
1128 | if (!ptr) { | ||
1129 | dev_err(&rproc->dev, | ||
1130 | "invalid coredump segment (%pad, %zu)\n", | ||
1131 | &segment->da, segment->size); | ||
1132 | memset(data + offset, 0xff, segment->size); | ||
1133 | } else { | ||
1134 | memcpy(data + offset, ptr, segment->size); | ||
1135 | } | ||
1136 | |||
1137 | offset += phdr->p_filesz; | ||
1138 | phdr++; | ||
1139 | } | ||
1140 | |||
1141 | dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL); | ||
1142 | } | ||
1143 | |||
1144 | /** | ||
1021 | * rproc_trigger_recovery() - recover a remoteproc | 1145 | * rproc_trigger_recovery() - recover a remoteproc |
1022 | * @rproc: the remote processor | 1146 | * @rproc: the remote processor |
1023 | * | 1147 | * |
@@ -1039,10 +1163,13 @@ int rproc_trigger_recovery(struct rproc *rproc) | |||
1039 | if (ret) | 1163 | if (ret) |
1040 | return ret; | 1164 | return ret; |
1041 | 1165 | ||
1042 | ret = rproc_stop(rproc); | 1166 | ret = rproc_stop(rproc, false); |
1043 | if (ret) | 1167 | if (ret) |
1044 | goto unlock_mutex; | 1168 | goto unlock_mutex; |
1045 | 1169 | ||
1170 | /* generate coredump */ | ||
1171 | rproc_coredump(rproc); | ||
1172 | |||
1046 | /* load firmware */ | 1173 | /* load firmware */ |
1047 | ret = request_firmware(&firmware_p, rproc->firmware, dev); | 1174 | ret = request_firmware(&firmware_p, rproc->firmware, dev); |
1048 | if (ret < 0) { | 1175 | if (ret < 0) { |
@@ -1189,7 +1316,7 @@ void rproc_shutdown(struct rproc *rproc) | |||
1189 | if (!atomic_dec_and_test(&rproc->power)) | 1316 | if (!atomic_dec_and_test(&rproc->power)) |
1190 | goto out; | 1317 | goto out; |
1191 | 1318 | ||
1192 | ret = rproc_stop(rproc); | 1319 | ret = rproc_stop(rproc, true); |
1193 | if (ret) { | 1320 | if (ret) { |
1194 | atomic_inc(&rproc->power); | 1321 | atomic_inc(&rproc->power); |
1195 | goto out; | 1322 | goto out; |
@@ -1428,7 +1555,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, | |||
1428 | /* Default to ELF loader if no load function is specified */ | 1555 | /* Default to ELF loader if no load function is specified */ |
1429 | if (!rproc->ops->load) { | 1556 | if (!rproc->ops->load) { |
1430 | rproc->ops->load = rproc_elf_load_segments; | 1557 | rproc->ops->load = rproc_elf_load_segments; |
1431 | rproc->ops->load_rsc_table = rproc_elf_load_rsc_table; | 1558 | rproc->ops->parse_fw = rproc_elf_load_rsc_table; |
1432 | rproc->ops->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table; | 1559 | rproc->ops->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table; |
1433 | rproc->ops->sanity_check = rproc_elf_sanity_check; | 1560 | rproc->ops->sanity_check = rproc_elf_sanity_check; |
1434 | rproc->ops->get_boot_addr = rproc_elf_get_boot_addr; | 1561 | rproc->ops->get_boot_addr = rproc_elf_get_boot_addr; |
@@ -1443,6 +1570,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, | |||
1443 | INIT_LIST_HEAD(&rproc->traces); | 1570 | INIT_LIST_HEAD(&rproc->traces); |
1444 | INIT_LIST_HEAD(&rproc->rvdevs); | 1571 | INIT_LIST_HEAD(&rproc->rvdevs); |
1445 | INIT_LIST_HEAD(&rproc->subdevs); | 1572 | INIT_LIST_HEAD(&rproc->subdevs); |
1573 | INIT_LIST_HEAD(&rproc->dump_segments); | ||
1446 | 1574 | ||
1447 | INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); | 1575 | INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); |
1448 | 1576 | ||
@@ -1535,7 +1663,7 @@ EXPORT_SYMBOL(rproc_del); | |||
1535 | void rproc_add_subdev(struct rproc *rproc, | 1663 | void rproc_add_subdev(struct rproc *rproc, |
1536 | struct rproc_subdev *subdev, | 1664 | struct rproc_subdev *subdev, |
1537 | int (*probe)(struct rproc_subdev *subdev), | 1665 | int (*probe)(struct rproc_subdev *subdev), |
1538 | void (*remove)(struct rproc_subdev *subdev)) | 1666 | void (*remove)(struct rproc_subdev *subdev, bool crashed)) |
1539 | { | 1667 | { |
1540 | subdev->probe = probe; | 1668 | subdev->probe = probe; |
1541 | subdev->remove = remove; | 1669 | subdev->remove = remove; |
diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 55a2950c5cb7..7570beb035b5 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h | |||
@@ -88,11 +88,10 @@ int rproc_load_segments(struct rproc *rproc, const struct firmware *fw) | |||
88 | return -EINVAL; | 88 | return -EINVAL; |
89 | } | 89 | } |
90 | 90 | ||
91 | static inline int rproc_load_rsc_table(struct rproc *rproc, | 91 | static inline int rproc_parse_fw(struct rproc *rproc, const struct firmware *fw) |
92 | const struct firmware *fw) | ||
93 | { | 92 | { |
94 | if (rproc->ops->load_rsc_table) | 93 | if (rproc->ops->parse_fw) |
95 | return rproc->ops->load_rsc_table(rproc, fw); | 94 | return rproc->ops->parse_fw(rproc, fw); |
96 | 95 | ||
97 | return 0; | 96 | return 0; |
98 | } | 97 | } |
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index a993d19fa562..5c4535b545cc 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig | |||
@@ -37,7 +37,7 @@ config QCOM_PM | |||
37 | 37 | ||
38 | config QCOM_QMI_HELPERS | 38 | config QCOM_QMI_HELPERS |
39 | tristate | 39 | tristate |
40 | depends on ARCH_QCOM | 40 | depends on ARCH_QCOM && NET |
41 | help | 41 | help |
42 | Helper library for handling QMI encoded messages. QMI encoded | 42 | Helper library for handling QMI encoded messages. QMI encoded |
43 | messages are used in communication between the majority of QRTR | 43 | messages are used in communication between the majority of QRTR |
diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index 08bd8549242a..17b314d9a148 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c | |||
@@ -83,12 +83,14 @@ EXPORT_SYMBOL_GPL(qcom_mdt_get_size); | |||
83 | * @mem_region: allocated memory region to load firmware into | 83 | * @mem_region: allocated memory region to load firmware into |
84 | * @mem_phys: physical address of allocated memory region | 84 | * @mem_phys: physical address of allocated memory region |
85 | * @mem_size: size of the allocated memory region | 85 | * @mem_size: size of the allocated memory region |
86 | * @reloc_base: adjusted physical address after relocation | ||
86 | * | 87 | * |
87 | * Returns 0 on success, negative errno otherwise. | 88 | * Returns 0 on success, negative errno otherwise. |
88 | */ | 89 | */ |
89 | int qcom_mdt_load(struct device *dev, const struct firmware *fw, | 90 | int qcom_mdt_load(struct device *dev, const struct firmware *fw, |
90 | const char *firmware, int pas_id, void *mem_region, | 91 | const char *firmware, int pas_id, void *mem_region, |
91 | phys_addr_t mem_phys, size_t mem_size) | 92 | phys_addr_t mem_phys, size_t mem_size, |
93 | phys_addr_t *reloc_base) | ||
92 | { | 94 | { |
93 | const struct elf32_phdr *phdrs; | 95 | const struct elf32_phdr *phdrs; |
94 | const struct elf32_phdr *phdr; | 96 | const struct elf32_phdr *phdr; |
@@ -192,6 +194,9 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw, | |||
192 | memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); | 194 | memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); |
193 | } | 195 | } |
194 | 196 | ||
197 | if (reloc_base) | ||
198 | *reloc_base = mem_reloc; | ||
199 | |||
195 | out: | 200 | out: |
196 | kfree(fw_name); | 201 | kfree(fw_name); |
197 | 202 | ||
diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 728d421fffe9..d09a9c7af109 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h | |||
@@ -344,7 +344,7 @@ struct rproc_ops { | |||
344 | int (*stop)(struct rproc *rproc); | 344 | int (*stop)(struct rproc *rproc); |
345 | void (*kick)(struct rproc *rproc, int vqid); | 345 | void (*kick)(struct rproc *rproc, int vqid); |
346 | void * (*da_to_va)(struct rproc *rproc, u64 da, int len); | 346 | void * (*da_to_va)(struct rproc *rproc, u64 da, int len); |
347 | int (*load_rsc_table)(struct rproc *rproc, const struct firmware *fw); | 347 | int (*parse_fw)(struct rproc *rproc, const struct firmware *fw); |
348 | struct resource_table *(*find_loaded_rsc_table)( | 348 | struct resource_table *(*find_loaded_rsc_table)( |
349 | struct rproc *rproc, const struct firmware *fw); | 349 | struct rproc *rproc, const struct firmware *fw); |
350 | int (*load)(struct rproc *rproc, const struct firmware *fw); | 350 | int (*load)(struct rproc *rproc, const struct firmware *fw); |
@@ -395,6 +395,21 @@ enum rproc_crash_type { | |||
395 | }; | 395 | }; |
396 | 396 | ||
397 | /** | 397 | /** |
398 | * struct rproc_dump_segment - segment info from ELF header | ||
399 | * @node: list node related to the rproc segment list | ||
400 | * @da: device address of the segment | ||
401 | * @size: size of the segment | ||
402 | */ | ||
403 | struct rproc_dump_segment { | ||
404 | struct list_head node; | ||
405 | |||
406 | dma_addr_t da; | ||
407 | size_t size; | ||
408 | |||
409 | loff_t offset; | ||
410 | }; | ||
411 | |||
412 | /** | ||
398 | * struct rproc - represents a physical remote processor device | 413 | * struct rproc - represents a physical remote processor device |
399 | * @node: list node of this rproc object | 414 | * @node: list node of this rproc object |
400 | * @domain: iommu domain | 415 | * @domain: iommu domain |
@@ -424,6 +439,7 @@ enum rproc_crash_type { | |||
424 | * @cached_table: copy of the resource table | 439 | * @cached_table: copy of the resource table |
425 | * @table_sz: size of @cached_table | 440 | * @table_sz: size of @cached_table |
426 | * @has_iommu: flag to indicate if remote processor is behind an MMU | 441 | * @has_iommu: flag to indicate if remote processor is behind an MMU |
442 | * @dump_segments: list of segments in the firmware | ||
427 | */ | 443 | */ |
428 | struct rproc { | 444 | struct rproc { |
429 | struct list_head node; | 445 | struct list_head node; |
@@ -455,19 +471,21 @@ struct rproc { | |||
455 | size_t table_sz; | 471 | size_t table_sz; |
456 | bool has_iommu; | 472 | bool has_iommu; |
457 | bool auto_boot; | 473 | bool auto_boot; |
474 | struct list_head dump_segments; | ||
458 | }; | 475 | }; |
459 | 476 | ||
460 | /** | 477 | /** |
461 | * struct rproc_subdev - subdevice tied to a remoteproc | 478 | * struct rproc_subdev - subdevice tied to a remoteproc |
462 | * @node: list node related to the rproc subdevs list | 479 | * @node: list node related to the rproc subdevs list |
463 | * @probe: probe function, called as the rproc is started | 480 | * @probe: probe function, called as the rproc is started |
464 | * @remove: remove function, called as the rproc is stopped | 481 | * @remove: remove function, called as the rproc is being stopped, the @crashed |
482 | * parameter indicates if this originates from the a recovery | ||
465 | */ | 483 | */ |
466 | struct rproc_subdev { | 484 | struct rproc_subdev { |
467 | struct list_head node; | 485 | struct list_head node; |
468 | 486 | ||
469 | int (*probe)(struct rproc_subdev *subdev); | 487 | int (*probe)(struct rproc_subdev *subdev); |
470 | void (*remove)(struct rproc_subdev *subdev); | 488 | void (*remove)(struct rproc_subdev *subdev, bool crashed); |
471 | }; | 489 | }; |
472 | 490 | ||
473 | /* we currently support only two vrings per rvdev */ | 491 | /* we currently support only two vrings per rvdev */ |
@@ -534,6 +552,7 @@ void rproc_free(struct rproc *rproc); | |||
534 | int rproc_boot(struct rproc *rproc); | 552 | int rproc_boot(struct rproc *rproc); |
535 | void rproc_shutdown(struct rproc *rproc); | 553 | void rproc_shutdown(struct rproc *rproc); |
536 | void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type); | 554 | void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type); |
555 | int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size); | ||
537 | 556 | ||
538 | static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev) | 557 | static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev) |
539 | { | 558 | { |
@@ -550,7 +569,7 @@ static inline struct rproc *vdev_to_rproc(struct virtio_device *vdev) | |||
550 | void rproc_add_subdev(struct rproc *rproc, | 569 | void rproc_add_subdev(struct rproc *rproc, |
551 | struct rproc_subdev *subdev, | 570 | struct rproc_subdev *subdev, |
552 | int (*probe)(struct rproc_subdev *subdev), | 571 | int (*probe)(struct rproc_subdev *subdev), |
553 | void (*remove)(struct rproc_subdev *subdev)); | 572 | void (*remove)(struct rproc_subdev *subdev, bool graceful)); |
554 | 573 | ||
555 | void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev); | 574 | void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev); |
556 | 575 | ||
diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index bd8e0864b059..5b98bbdabc25 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h | |||
@@ -14,6 +14,7 @@ struct firmware; | |||
14 | ssize_t qcom_mdt_get_size(const struct firmware *fw); | 14 | ssize_t qcom_mdt_get_size(const struct firmware *fw); |
15 | int qcom_mdt_load(struct device *dev, const struct firmware *fw, | 15 | int qcom_mdt_load(struct device *dev, const struct firmware *fw, |
16 | const char *fw_name, int pas_id, void *mem_region, | 16 | const char *fw_name, int pas_id, void *mem_region, |
17 | phys_addr_t mem_phys, size_t mem_size); | 17 | phys_addr_t mem_phys, size_t mem_size, |
18 | phys_addr_t *reloc_base); | ||
18 | 19 | ||
19 | #endif | 20 | #endif |
diff --git a/samples/Kconfig b/samples/Kconfig index f524f551718e..3db002b9e1d3 100644 --- a/samples/Kconfig +++ b/samples/Kconfig | |||
@@ -62,6 +62,16 @@ config SAMPLE_KDB | |||
62 | Build an example of how to dynamically add the hello | 62 | Build an example of how to dynamically add the hello |
63 | command to the kdb shell. | 63 | command to the kdb shell. |
64 | 64 | ||
65 | config SAMPLE_QMI_CLIENT | ||
66 | tristate "Build qmi client sample -- loadable modules only" | ||
67 | depends on m | ||
68 | depends on ARCH_QCOM | ||
69 | depends on NET | ||
70 | select QCOM_QMI_HELPERS | ||
71 | help | ||
72 | Build an QMI client sample driver, which demonstrates how to | ||
73 | communicate with a remote QRTR service, using QMI encoded messages. | ||
74 | |||
65 | config SAMPLE_RPMSG_CLIENT | 75 | config SAMPLE_RPMSG_CLIENT |
66 | tristate "Build rpmsg client sample -- loadable modules only" | 76 | tristate "Build rpmsg client sample -- loadable modules only" |
67 | depends on RPMSG && m | 77 | depends on RPMSG && m |
diff --git a/samples/Makefile b/samples/Makefile index 70cf3758dcf2..bd601c038b86 100644 --- a/samples/Makefile +++ b/samples/Makefile | |||
@@ -3,4 +3,4 @@ | |||
3 | obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ | 3 | obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ |
4 | hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ | 4 | hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ |
5 | configfs/ connector/ v4l/ trace_printk/ \ | 5 | configfs/ connector/ v4l/ trace_printk/ \ |
6 | vfio-mdev/ statx/ | 6 | vfio-mdev/ statx/ qmi/ |
diff --git a/samples/qmi/Makefile b/samples/qmi/Makefile new file mode 100644 index 000000000000..2b111d2769df --- /dev/null +++ b/samples/qmi/Makefile | |||
@@ -0,0 +1 @@ | |||
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi_sample_client.o | |||
diff --git a/samples/qmi/qmi_sample_client.c b/samples/qmi/qmi_sample_client.c new file mode 100644 index 000000000000..c9e7276c3d83 --- /dev/null +++ b/samples/qmi/qmi_sample_client.c | |||
@@ -0,0 +1,622 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Sample in-kernel QMI client driver | ||
4 | * | ||
5 | * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. | ||
6 | * Copyright (C) 2017 Linaro Ltd. | ||
7 | */ | ||
8 | #include <linux/kernel.h> | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/debugfs.h> | ||
11 | #include <linux/device.h> | ||
12 | #include <linux/platform_device.h> | ||
13 | #include <linux/qrtr.h> | ||
14 | #include <linux/net.h> | ||
15 | #include <linux/completion.h> | ||
16 | #include <linux/idr.h> | ||
17 | #include <linux/string.h> | ||
18 | #include <net/sock.h> | ||
19 | #include <linux/soc/qcom/qmi.h> | ||
20 | |||
21 | #define PING_REQ1_TLV_TYPE 0x1 | ||
22 | #define PING_RESP1_TLV_TYPE 0x2 | ||
23 | #define PING_OPT1_TLV_TYPE 0x10 | ||
24 | #define PING_OPT2_TLV_TYPE 0x11 | ||
25 | |||
26 | #define DATA_REQ1_TLV_TYPE 0x1 | ||
27 | #define DATA_RESP1_TLV_TYPE 0x2 | ||
28 | #define DATA_OPT1_TLV_TYPE 0x10 | ||
29 | #define DATA_OPT2_TLV_TYPE 0x11 | ||
30 | |||
31 | #define TEST_MED_DATA_SIZE_V01 8192 | ||
32 | #define TEST_MAX_NAME_SIZE_V01 255 | ||
33 | |||
34 | #define TEST_PING_REQ_MSG_ID_V01 0x20 | ||
35 | #define TEST_DATA_REQ_MSG_ID_V01 0x21 | ||
36 | |||
37 | #define TEST_PING_REQ_MAX_MSG_LEN_V01 266 | ||
38 | #define TEST_DATA_REQ_MAX_MSG_LEN_V01 8456 | ||
39 | |||
40 | struct test_name_type_v01 { | ||
41 | u32 name_len; | ||
42 | char name[TEST_MAX_NAME_SIZE_V01]; | ||
43 | }; | ||
44 | |||
45 | static struct qmi_elem_info test_name_type_v01_ei[] = { | ||
46 | { | ||
47 | .data_type = QMI_DATA_LEN, | ||
48 | .elem_len = 1, | ||
49 | .elem_size = sizeof(u8), | ||
50 | .array_type = NO_ARRAY, | ||
51 | .tlv_type = QMI_COMMON_TLV_TYPE, | ||
52 | .offset = offsetof(struct test_name_type_v01, | ||
53 | name_len), | ||
54 | }, | ||
55 | { | ||
56 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
57 | .elem_len = TEST_MAX_NAME_SIZE_V01, | ||
58 | .elem_size = sizeof(char), | ||
59 | .array_type = VAR_LEN_ARRAY, | ||
60 | .tlv_type = QMI_COMMON_TLV_TYPE, | ||
61 | .offset = offsetof(struct test_name_type_v01, | ||
62 | name), | ||
63 | }, | ||
64 | {} | ||
65 | }; | ||
66 | |||
67 | struct test_ping_req_msg_v01 { | ||
68 | char ping[4]; | ||
69 | |||
70 | u8 client_name_valid; | ||
71 | struct test_name_type_v01 client_name; | ||
72 | }; | ||
73 | |||
74 | static struct qmi_elem_info test_ping_req_msg_v01_ei[] = { | ||
75 | { | ||
76 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
77 | .elem_len = 4, | ||
78 | .elem_size = sizeof(char), | ||
79 | .array_type = STATIC_ARRAY, | ||
80 | .tlv_type = PING_REQ1_TLV_TYPE, | ||
81 | .offset = offsetof(struct test_ping_req_msg_v01, | ||
82 | ping), | ||
83 | }, | ||
84 | { | ||
85 | .data_type = QMI_OPT_FLAG, | ||
86 | .elem_len = 1, | ||
87 | .elem_size = sizeof(u8), | ||
88 | .array_type = NO_ARRAY, | ||
89 | .tlv_type = PING_OPT1_TLV_TYPE, | ||
90 | .offset = offsetof(struct test_ping_req_msg_v01, | ||
91 | client_name_valid), | ||
92 | }, | ||
93 | { | ||
94 | .data_type = QMI_STRUCT, | ||
95 | .elem_len = 1, | ||
96 | .elem_size = sizeof(struct test_name_type_v01), | ||
97 | .array_type = NO_ARRAY, | ||
98 | .tlv_type = PING_OPT1_TLV_TYPE, | ||
99 | .offset = offsetof(struct test_ping_req_msg_v01, | ||
100 | client_name), | ||
101 | .ei_array = test_name_type_v01_ei, | ||
102 | }, | ||
103 | {} | ||
104 | }; | ||
105 | |||
106 | struct test_ping_resp_msg_v01 { | ||
107 | struct qmi_response_type_v01 resp; | ||
108 | |||
109 | u8 pong_valid; | ||
110 | char pong[4]; | ||
111 | |||
112 | u8 service_name_valid; | ||
113 | struct test_name_type_v01 service_name; | ||
114 | }; | ||
115 | |||
116 | static struct qmi_elem_info test_ping_resp_msg_v01_ei[] = { | ||
117 | { | ||
118 | .data_type = QMI_STRUCT, | ||
119 | .elem_len = 1, | ||
120 | .elem_size = sizeof(struct qmi_response_type_v01), | ||
121 | .array_type = NO_ARRAY, | ||
122 | .tlv_type = PING_RESP1_TLV_TYPE, | ||
123 | .offset = offsetof(struct test_ping_resp_msg_v01, | ||
124 | resp), | ||
125 | .ei_array = qmi_response_type_v01_ei, | ||
126 | }, | ||
127 | { | ||
128 | .data_type = QMI_OPT_FLAG, | ||
129 | .elem_len = 1, | ||
130 | .elem_size = sizeof(u8), | ||
131 | .array_type = NO_ARRAY, | ||
132 | .tlv_type = PING_OPT1_TLV_TYPE, | ||
133 | .offset = offsetof(struct test_ping_resp_msg_v01, | ||
134 | pong_valid), | ||
135 | }, | ||
136 | { | ||
137 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
138 | .elem_len = 4, | ||
139 | .elem_size = sizeof(char), | ||
140 | .array_type = STATIC_ARRAY, | ||
141 | .tlv_type = PING_OPT1_TLV_TYPE, | ||
142 | .offset = offsetof(struct test_ping_resp_msg_v01, | ||
143 | pong), | ||
144 | }, | ||
145 | { | ||
146 | .data_type = QMI_OPT_FLAG, | ||
147 | .elem_len = 1, | ||
148 | .elem_size = sizeof(u8), | ||
149 | .array_type = NO_ARRAY, | ||
150 | .tlv_type = PING_OPT2_TLV_TYPE, | ||
151 | .offset = offsetof(struct test_ping_resp_msg_v01, | ||
152 | service_name_valid), | ||
153 | }, | ||
154 | { | ||
155 | .data_type = QMI_STRUCT, | ||
156 | .elem_len = 1, | ||
157 | .elem_size = sizeof(struct test_name_type_v01), | ||
158 | .array_type = NO_ARRAY, | ||
159 | .tlv_type = PING_OPT2_TLV_TYPE, | ||
160 | .offset = offsetof(struct test_ping_resp_msg_v01, | ||
161 | service_name), | ||
162 | .ei_array = test_name_type_v01_ei, | ||
163 | }, | ||
164 | {} | ||
165 | }; | ||
166 | |||
167 | struct test_data_req_msg_v01 { | ||
168 | u32 data_len; | ||
169 | u8 data[TEST_MED_DATA_SIZE_V01]; | ||
170 | |||
171 | u8 client_name_valid; | ||
172 | struct test_name_type_v01 client_name; | ||
173 | }; | ||
174 | |||
175 | static struct qmi_elem_info test_data_req_msg_v01_ei[] = { | ||
176 | { | ||
177 | .data_type = QMI_DATA_LEN, | ||
178 | .elem_len = 1, | ||
179 | .elem_size = sizeof(u32), | ||
180 | .array_type = NO_ARRAY, | ||
181 | .tlv_type = DATA_REQ1_TLV_TYPE, | ||
182 | .offset = offsetof(struct test_data_req_msg_v01, | ||
183 | data_len), | ||
184 | }, | ||
185 | { | ||
186 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
187 | .elem_len = TEST_MED_DATA_SIZE_V01, | ||
188 | .elem_size = sizeof(u8), | ||
189 | .array_type = VAR_LEN_ARRAY, | ||
190 | .tlv_type = DATA_REQ1_TLV_TYPE, | ||
191 | .offset = offsetof(struct test_data_req_msg_v01, | ||
192 | data), | ||
193 | }, | ||
194 | { | ||
195 | .data_type = QMI_OPT_FLAG, | ||
196 | .elem_len = 1, | ||
197 | .elem_size = sizeof(u8), | ||
198 | .array_type = NO_ARRAY, | ||
199 | .tlv_type = DATA_OPT1_TLV_TYPE, | ||
200 | .offset = offsetof(struct test_data_req_msg_v01, | ||
201 | client_name_valid), | ||
202 | }, | ||
203 | { | ||
204 | .data_type = QMI_STRUCT, | ||
205 | .elem_len = 1, | ||
206 | .elem_size = sizeof(struct test_name_type_v01), | ||
207 | .array_type = NO_ARRAY, | ||
208 | .tlv_type = DATA_OPT1_TLV_TYPE, | ||
209 | .offset = offsetof(struct test_data_req_msg_v01, | ||
210 | client_name), | ||
211 | .ei_array = test_name_type_v01_ei, | ||
212 | }, | ||
213 | {} | ||
214 | }; | ||
215 | |||
216 | struct test_data_resp_msg_v01 { | ||
217 | struct qmi_response_type_v01 resp; | ||
218 | |||
219 | u8 data_valid; | ||
220 | u32 data_len; | ||
221 | u8 data[TEST_MED_DATA_SIZE_V01]; | ||
222 | |||
223 | u8 service_name_valid; | ||
224 | struct test_name_type_v01 service_name; | ||
225 | }; | ||
226 | |||
227 | static struct qmi_elem_info test_data_resp_msg_v01_ei[] = { | ||
228 | { | ||
229 | .data_type = QMI_STRUCT, | ||
230 | .elem_len = 1, | ||
231 | .elem_size = sizeof(struct qmi_response_type_v01), | ||
232 | .array_type = NO_ARRAY, | ||
233 | .tlv_type = DATA_RESP1_TLV_TYPE, | ||
234 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
235 | resp), | ||
236 | .ei_array = qmi_response_type_v01_ei, | ||
237 | }, | ||
238 | { | ||
239 | .data_type = QMI_OPT_FLAG, | ||
240 | .elem_len = 1, | ||
241 | .elem_size = sizeof(u8), | ||
242 | .array_type = NO_ARRAY, | ||
243 | .tlv_type = DATA_OPT1_TLV_TYPE, | ||
244 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
245 | data_valid), | ||
246 | }, | ||
247 | { | ||
248 | .data_type = QMI_DATA_LEN, | ||
249 | .elem_len = 1, | ||
250 | .elem_size = sizeof(u32), | ||
251 | .array_type = NO_ARRAY, | ||
252 | .tlv_type = DATA_OPT1_TLV_TYPE, | ||
253 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
254 | data_len), | ||
255 | }, | ||
256 | { | ||
257 | .data_type = QMI_UNSIGNED_1_BYTE, | ||
258 | .elem_len = TEST_MED_DATA_SIZE_V01, | ||
259 | .elem_size = sizeof(u8), | ||
260 | .array_type = VAR_LEN_ARRAY, | ||
261 | .tlv_type = DATA_OPT1_TLV_TYPE, | ||
262 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
263 | data), | ||
264 | }, | ||
265 | { | ||
266 | .data_type = QMI_OPT_FLAG, | ||
267 | .elem_len = 1, | ||
268 | .elem_size = sizeof(u8), | ||
269 | .array_type = NO_ARRAY, | ||
270 | .tlv_type = DATA_OPT2_TLV_TYPE, | ||
271 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
272 | service_name_valid), | ||
273 | }, | ||
274 | { | ||
275 | .data_type = QMI_STRUCT, | ||
276 | .elem_len = 1, | ||
277 | .elem_size = sizeof(struct test_name_type_v01), | ||
278 | .array_type = NO_ARRAY, | ||
279 | .tlv_type = DATA_OPT2_TLV_TYPE, | ||
280 | .offset = offsetof(struct test_data_resp_msg_v01, | ||
281 | service_name), | ||
282 | .ei_array = test_name_type_v01_ei, | ||
283 | }, | ||
284 | {} | ||
285 | }; | ||
286 | |||
287 | /* | ||
288 | * ping_write() - ping_pong debugfs file write handler | ||
289 | * @file: debugfs file context | ||
290 | * @user_buf: reference to the user data (ignored) | ||
291 | * @count: number of bytes in @user_buf | ||
292 | * @ppos: offset in @file to write | ||
293 | * | ||
294 | * This function allows user space to send out a ping_pong QMI encoded message | ||
295 | * to the associated remote test service and will return with the result of the | ||
296 | * transaction. It serves as an example of how to provide a custom response | ||
297 | * handler. | ||
298 | * | ||
299 | * Return: @count, or negative errno on failure. | ||
300 | */ | ||
301 | static ssize_t ping_write(struct file *file, const char __user *user_buf, | ||
302 | size_t count, loff_t *ppos) | ||
303 | { | ||
304 | struct qmi_handle *qmi = file->private_data; | ||
305 | struct test_ping_req_msg_v01 req = {}; | ||
306 | struct qmi_txn txn; | ||
307 | int ret; | ||
308 | |||
309 | memcpy(req.ping, "ping", sizeof(req.ping)); | ||
310 | |||
311 | ret = qmi_txn_init(qmi, &txn, NULL, NULL); | ||
312 | if (ret < 0) | ||
313 | return ret; | ||
314 | |||
315 | ret = qmi_send_request(qmi, NULL, &txn, | ||
316 | TEST_PING_REQ_MSG_ID_V01, | ||
317 | TEST_PING_REQ_MAX_MSG_LEN_V01, | ||
318 | test_ping_req_msg_v01_ei, &req); | ||
319 | if (ret < 0) { | ||
320 | qmi_txn_cancel(&txn); | ||
321 | return ret; | ||
322 | } | ||
323 | |||
324 | ret = qmi_txn_wait(&txn, 5 * HZ); | ||
325 | if (ret < 0) | ||
326 | count = ret; | ||
327 | |||
328 | return count; | ||
329 | } | ||
330 | |||
331 | static const struct file_operations ping_fops = { | ||
332 | .open = simple_open, | ||
333 | .write = ping_write, | ||
334 | }; | ||
335 | |||
336 | static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, | ||
337 | struct qmi_txn *txn, const void *data) | ||
338 | { | ||
339 | const struct test_ping_resp_msg_v01 *resp = data; | ||
340 | |||
341 | if (!txn) { | ||
342 | pr_err("spurious ping response\n"); | ||
343 | return; | ||
344 | } | ||
345 | |||
346 | if (resp->resp.result == QMI_RESULT_FAILURE_V01) | ||
347 | txn->result = -ENXIO; | ||
348 | else if (!resp->pong_valid || memcmp(resp->pong, "pong", 4)) | ||
349 | txn->result = -EINVAL; | ||
350 | |||
351 | complete(&txn->completion); | ||
352 | } | ||
353 | |||
354 | /* | ||
355 | * data_write() - data debugfs file write handler | ||
356 | * @file: debugfs file context | ||
357 | * @user_buf: reference to the user data | ||
358 | * @count: number of bytes in @user_buf | ||
359 | * @ppos: offset in @file to write | ||
360 | * | ||
361 | * This function allows user space to send out a data QMI encoded message to | ||
362 | * the associated remote test service and will return with the result of the | ||
363 | * transaction. It serves as an example of how to have the QMI helpers decode a | ||
364 | * transaction response into a provided object automatically. | ||
365 | * | ||
366 | * Return: @count, or negative errno on failure. | ||
367 | */ | ||
368 | static ssize_t data_write(struct file *file, const char __user *user_buf, | ||
369 | size_t count, loff_t *ppos) | ||
370 | |||
371 | { | ||
372 | struct qmi_handle *qmi = file->private_data; | ||
373 | struct test_data_resp_msg_v01 *resp; | ||
374 | struct test_data_req_msg_v01 *req; | ||
375 | struct qmi_txn txn; | ||
376 | int ret; | ||
377 | |||
378 | req = kzalloc(sizeof(*req), GFP_KERNEL); | ||
379 | if (!req) | ||
380 | return -ENOMEM; | ||
381 | |||
382 | resp = kzalloc(sizeof(*resp), GFP_KERNEL); | ||
383 | if (!resp) { | ||
384 | kfree(req); | ||
385 | return -ENOMEM; | ||
386 | } | ||
387 | |||
388 | req->data_len = min_t(size_t, sizeof(req->data), count); | ||
389 | if (copy_from_user(req->data, user_buf, req->data_len)) { | ||
390 | ret = -EFAULT; | ||
391 | goto out; | ||
392 | } | ||
393 | |||
394 | ret = qmi_txn_init(qmi, &txn, test_data_resp_msg_v01_ei, resp); | ||
395 | if (ret < 0) | ||
396 | goto out; | ||
397 | |||
398 | ret = qmi_send_request(qmi, NULL, &txn, | ||
399 | TEST_DATA_REQ_MSG_ID_V01, | ||
400 | TEST_DATA_REQ_MAX_MSG_LEN_V01, | ||
401 | test_data_req_msg_v01_ei, req); | ||
402 | if (ret < 0) { | ||
403 | qmi_txn_cancel(&txn); | ||
404 | goto out; | ||
405 | } | ||
406 | |||
407 | ret = qmi_txn_wait(&txn, 5 * HZ); | ||
408 | if (ret < 0) { | ||
409 | goto out; | ||
410 | } else if (!resp->data_valid || | ||
411 | resp->data_len != req->data_len || | ||
412 | memcmp(resp->data, req->data, req->data_len)) { | ||
413 | pr_err("response data doesn't match expectation\n"); | ||
414 | ret = -EINVAL; | ||
415 | goto out; | ||
416 | } | ||
417 | |||
418 | ret = count; | ||
419 | |||
420 | out: | ||
421 | kfree(resp); | ||
422 | kfree(req); | ||
423 | |||
424 | return ret; | ||
425 | } | ||
426 | |||
427 | static const struct file_operations data_fops = { | ||
428 | .open = simple_open, | ||
429 | .write = data_write, | ||
430 | }; | ||
431 | |||
432 | static struct qmi_msg_handler qmi_sample_handlers[] = { | ||
433 | { | ||
434 | .type = QMI_RESPONSE, | ||
435 | .msg_id = TEST_PING_REQ_MSG_ID_V01, | ||
436 | .ei = test_ping_resp_msg_v01_ei, | ||
437 | .decoded_size = sizeof(struct test_ping_req_msg_v01), | ||
438 | .fn = ping_pong_cb | ||
439 | }, | ||
440 | {} | ||
441 | }; | ||
442 | |||
443 | struct qmi_sample { | ||
444 | struct qmi_handle qmi; | ||
445 | |||
446 | struct dentry *de_dir; | ||
447 | struct dentry *de_data; | ||
448 | struct dentry *de_ping; | ||
449 | }; | ||
450 | |||
451 | static struct dentry *qmi_debug_dir; | ||
452 | |||
453 | static int qmi_sample_probe(struct platform_device *pdev) | ||
454 | { | ||
455 | struct sockaddr_qrtr *sq; | ||
456 | struct qmi_sample *sample; | ||
457 | char path[20]; | ||
458 | int ret; | ||
459 | |||
460 | sample = devm_kzalloc(&pdev->dev, sizeof(*sample), GFP_KERNEL); | ||
461 | if (!sample) | ||
462 | return -ENOMEM; | ||
463 | |||
464 | ret = qmi_handle_init(&sample->qmi, TEST_DATA_REQ_MAX_MSG_LEN_V01, | ||
465 | NULL, | ||
466 | qmi_sample_handlers); | ||
467 | if (ret < 0) | ||
468 | return ret; | ||
469 | |||
470 | sq = dev_get_platdata(&pdev->dev); | ||
471 | ret = kernel_connect(sample->qmi.sock, (struct sockaddr *)sq, | ||
472 | sizeof(*sq), 0); | ||
473 | if (ret < 0) { | ||
474 | pr_err("failed to connect to remote service port\n"); | ||
475 | goto err_release_qmi_handle; | ||
476 | } | ||
477 | |||
478 | snprintf(path, sizeof(path), "%d:%d", sq->sq_node, sq->sq_port); | ||
479 | |||
480 | sample->de_dir = debugfs_create_dir(path, qmi_debug_dir); | ||
481 | if (IS_ERR(sample->de_dir)) { | ||
482 | ret = PTR_ERR(sample->de_dir); | ||
483 | goto err_release_qmi_handle; | ||
484 | } | ||
485 | |||
486 | sample->de_data = debugfs_create_file("data", 0600, sample->de_dir, | ||
487 | sample, &data_fops); | ||
488 | if (IS_ERR(sample->de_data)) { | ||
489 | ret = PTR_ERR(sample->de_data); | ||
490 | goto err_remove_de_dir; | ||
491 | } | ||
492 | |||
493 | sample->de_ping = debugfs_create_file("ping", 0600, sample->de_dir, | ||
494 | sample, &ping_fops); | ||
495 | if (IS_ERR(sample->de_ping)) { | ||
496 | ret = PTR_ERR(sample->de_ping); | ||
497 | goto err_remove_de_data; | ||
498 | } | ||
499 | |||
500 | platform_set_drvdata(pdev, sample); | ||
501 | |||
502 | return 0; | ||
503 | |||
504 | err_remove_de_data: | ||
505 | debugfs_remove(sample->de_data); | ||
506 | err_remove_de_dir: | ||
507 | debugfs_remove(sample->de_dir); | ||
508 | err_release_qmi_handle: | ||
509 | qmi_handle_release(&sample->qmi); | ||
510 | |||
511 | return ret; | ||
512 | } | ||
513 | |||
514 | static int qmi_sample_remove(struct platform_device *pdev) | ||
515 | { | ||
516 | struct qmi_sample *sample = platform_get_drvdata(pdev); | ||
517 | |||
518 | debugfs_remove(sample->de_ping); | ||
519 | debugfs_remove(sample->de_data); | ||
520 | debugfs_remove(sample->de_dir); | ||
521 | |||
522 | qmi_handle_release(&sample->qmi); | ||
523 | |||
524 | return 0; | ||
525 | } | ||
526 | |||
527 | static struct platform_driver qmi_sample_driver = { | ||
528 | .probe = qmi_sample_probe, | ||
529 | .remove = qmi_sample_remove, | ||
530 | .driver = { | ||
531 | .name = "qmi_sample_client", | ||
532 | }, | ||
533 | }; | ||
534 | |||
535 | static int qmi_sample_new_server(struct qmi_handle *qmi, | ||
536 | struct qmi_service *service) | ||
537 | { | ||
538 | struct platform_device *pdev; | ||
539 | struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port }; | ||
540 | int ret; | ||
541 | |||
542 | pdev = platform_device_alloc("qmi_sample_client", PLATFORM_DEVID_AUTO); | ||
543 | if (!pdev) | ||
544 | return -ENOMEM; | ||
545 | |||
546 | ret = platform_device_add_data(pdev, &sq, sizeof(sq)); | ||
547 | if (ret) | ||
548 | goto err_put_device; | ||
549 | |||
550 | ret = platform_device_add(pdev); | ||
551 | if (ret) | ||
552 | goto err_put_device; | ||
553 | |||
554 | service->priv = pdev; | ||
555 | |||
556 | return 0; | ||
557 | |||
558 | err_put_device: | ||
559 | platform_device_put(pdev); | ||
560 | |||
561 | return ret; | ||
562 | } | ||
563 | |||
564 | static void qmi_sample_del_server(struct qmi_handle *qmi, | ||
565 | struct qmi_service *service) | ||
566 | { | ||
567 | struct platform_device *pdev = service->priv; | ||
568 | |||
569 | platform_device_unregister(pdev); | ||
570 | } | ||
571 | |||
572 | static struct qmi_handle lookup_client; | ||
573 | |||
574 | static struct qmi_ops lookup_ops = { | ||
575 | .new_server = qmi_sample_new_server, | ||
576 | .del_server = qmi_sample_del_server, | ||
577 | }; | ||
578 | |||
579 | static int qmi_sample_init(void) | ||
580 | { | ||
581 | int ret; | ||
582 | |||
583 | qmi_debug_dir = debugfs_create_dir("qmi_sample", NULL); | ||
584 | if (IS_ERR(qmi_debug_dir)) { | ||
585 | pr_err("failed to create qmi_sample dir\n"); | ||
586 | return PTR_ERR(qmi_debug_dir); | ||
587 | } | ||
588 | |||
589 | ret = platform_driver_register(&qmi_sample_driver); | ||
590 | if (ret) | ||
591 | goto err_remove_debug_dir; | ||
592 | |||
593 | ret = qmi_handle_init(&lookup_client, 0, &lookup_ops, NULL); | ||
594 | if (ret < 0) | ||
595 | goto err_unregister_driver; | ||
596 | |||
597 | qmi_add_lookup(&lookup_client, 15, 0, 0); | ||
598 | |||
599 | return 0; | ||
600 | |||
601 | err_unregister_driver: | ||
602 | platform_driver_unregister(&qmi_sample_driver); | ||
603 | err_remove_debug_dir: | ||
604 | debugfs_remove(qmi_debug_dir); | ||
605 | |||
606 | return ret; | ||
607 | } | ||
608 | |||
609 | static void qmi_sample_exit(void) | ||
610 | { | ||
611 | qmi_handle_release(&lookup_client); | ||
612 | |||
613 | platform_driver_unregister(&qmi_sample_driver); | ||
614 | |||
615 | debugfs_remove(qmi_debug_dir); | ||
616 | } | ||
617 | |||
618 | module_init(qmi_sample_init); | ||
619 | module_exit(qmi_sample_exit); | ||
620 | |||
621 | MODULE_DESCRIPTION("Sample QMI client driver"); | ||
622 | MODULE_LICENSE("GPL v2"); | ||