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 a4f68af..d39400e 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) */ if (to_adreno_gpu(gpu)->fwloc == FW_LOCATION_LEGACY) { ret = qcom_mdt_load(dev, fw, fwname, GPU_PAS_ID, - mem_region, mem_phys, mem_size); + mem_region, mem_phys, mem_size, NULL); } else { char newname[strlen("qcom/") + strlen(fwname) + 1]; sprintf(newname, "qcom/%s", fwname); ret = qcom_mdt_load(dev, fw, newname, GPU_PAS_ID, - mem_region, mem_phys, mem_size); + mem_region, mem_phys, mem_size, NULL); } if (ret) goto out; diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c index 521d4b3..c4a5778 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) } ret = qcom_mdt_load(dev, mdt, fwname, VENUS_PAS_ID, mem_va, mem_phys, - mem_size); + mem_size, NULL); release_firmware(mdt); diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index b609e1d..0272740 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -6,6 +6,7 @@ config REMOTEPROC select CRC32 select FW_LOADER select VIRTIO + select WANT_DEV_COREDUMP help Support for remote processors (such as DSP coprocessors). These are mainly used on embedded systems. @@ -90,6 +91,7 @@ config QCOM_ADSP_PIL depends on QCOM_SMEM depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n + depends on QCOM_SYSMON || QCOM_SYSMON=n select MFD_SYSCON select QCOM_MDT_LOADER select QCOM_RPROC_COMMON @@ -107,6 +109,7 @@ config QCOM_Q6V5_PIL depends on QCOM_SMEM depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n + depends on QCOM_SYSMON || QCOM_SYSMON=n select MFD_SYSCON select QCOM_RPROC_COMMON select QCOM_SCM @@ -114,12 +117,28 @@ config QCOM_Q6V5_PIL Say y here to support the Qualcomm Peripherial Image Loader for the Hexagon V5 based remote processors. +config QCOM_SYSMON + tristate "Qualcomm sysmon driver" + depends on RPMSG + depends on ARCH_QCOM + depends on NET + select QCOM_QMI_HELPERS + help + The sysmon driver implements a sysmon QMI client and a handler for + the sys_mon SMD and GLINK channel, which are used for graceful + shutdown, retrieving failure information and propagating information + about other subsystems being shut down. + + Say y here if your system runs firmware on any other subsystems, e.g. + modem or DSP. + config QCOM_WCNSS_PIL tristate "Qualcomm WCNSS Peripheral Image Loader" depends on OF && ARCH_QCOM depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n) depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n depends on QCOM_SMEM + depends on QCOM_SYSMON || QCOM_SYSMON=n select QCOM_MDT_LOADER select QCOM_RPROC_COMMON select QCOM_SCM diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 6e16450..02627ed 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o +obj-$(CONFIG_QCOM_SYSMON) += qcom_sysmon.o obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o qcom_wcnss_pil-y += qcom_wcnss.o qcom_wcnss_pil-y += qcom_wcnss_iris.o diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c index 633268e..54c07fd 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) /* set some other name then imx */ rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops, NULL, sizeof(*priv)); - if (!rproc) { - ret = -ENOMEM; - goto err; - } + if (!rproc) + return -ENOMEM; dcfg = of_device_get_match_data(dev); - if (!dcfg) - return -EINVAL; + if (!dcfg) { + ret = -EINVAL; + goto err_put_rproc; + } priv = rproc->priv; priv->rproc = rproc; @@ -359,8 +359,8 @@ static int imx_rproc_probe(struct platform_device *pdev) priv->clk = devm_clk_get(dev, NULL); if (IS_ERR(priv->clk)) { dev_err(dev, "Failed to get clock\n"); - rproc_free(rproc); - return PTR_ERR(priv->clk); + ret = PTR_ERR(priv->clk); + goto err_put_rproc; } /* @@ -370,8 +370,7 @@ static int imx_rproc_probe(struct platform_device *pdev) ret = clk_prepare_enable(priv->clk); if (ret) { dev_err(&rproc->dev, "Failed to enable clock\n"); - rproc_free(rproc); - return ret; + goto err_put_rproc; } ret = rproc_add(rproc); @@ -380,13 +379,13 @@ static int imx_rproc_probe(struct platform_device *pdev) goto err_put_clk; } - return ret; + return 0; err_put_clk: clk_disable_unprepare(priv->clk); err_put_rproc: rproc_free(rproc); -err: + return ret; } diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c index 373c167..89a86ce 100644 --- a/drivers/remoteproc/qcom_adsp_pil.c +++ b/drivers/remoteproc/qcom_adsp_pil.c @@ -38,7 +38,10 @@ struct adsp_data { const char *firmware_name; int pas_id; bool has_aggre2_clk; + const char *ssr_name; + const char *sysmon_name; + int ssctl_id; }; struct qcom_adsp { @@ -75,6 +78,7 @@ struct qcom_adsp { struct qcom_rproc_glink glink_subdev; struct qcom_rproc_subdev smd_subdev; struct qcom_rproc_ssr ssr_subdev; + struct qcom_sysmon *sysmon; }; 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) struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id, - adsp->mem_region, adsp->mem_phys, adsp->mem_size); + adsp->mem_region, adsp->mem_phys, adsp->mem_size, + &adsp->mem_reloc); + } static int adsp_start(struct rproc *rproc) @@ -177,6 +183,7 @@ static const struct rproc_ops adsp_ops = { .start = adsp_start, .stop = adsp_stop, .da_to_va = adsp_da_to_va, + .parse_fw = qcom_register_dump_segments, .load = adsp_load, }; @@ -201,9 +208,6 @@ static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); - if (!IS_ERR(msg)) - msg[0] = '\0'; - return IRQ_HANDLED; } @@ -398,6 +402,9 @@ static int adsp_probe(struct platform_device *pdev) qcom_add_glink_subdev(rproc, &adsp->glink_subdev); qcom_add_smd_subdev(rproc, &adsp->smd_subdev); qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name); + adsp->sysmon = qcom_add_sysmon_subdev(rproc, + desc->sysmon_name, + desc->ssctl_id); ret = rproc_add(rproc); if (ret) @@ -419,6 +426,7 @@ static int adsp_remove(struct platform_device *pdev) rproc_del(adsp->rproc); qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); + qcom_remove_sysmon_subdev(adsp->sysmon); qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev); qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev); rproc_free(adsp->rproc); @@ -432,6 +440,8 @@ static const struct adsp_data adsp_resource_init = { .pas_id = 1, .has_aggre2_clk = false, .ssr_name = "lpass", + .sysmon_name = "adsp", + .ssctl_id = 0x14, }; static const struct adsp_data slpi_resource_init = { @@ -440,6 +450,8 @@ static const struct adsp_data slpi_resource_init = { .pas_id = 12, .has_aggre2_clk = true, .ssr_name = "dsps", + .sysmon_name = "slpi", + .ssctl_id = 0x16, }; static const struct of_device_id adsp_of_match[] = { diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c index 0060249..acfc99f 100644 --- a/drivers/remoteproc/qcom_common.c +++ b/drivers/remoteproc/qcom_common.c @@ -22,6 +22,7 @@ #include <linux/remoteproc.h> #include <linux/rpmsg/qcom_glink.h> #include <linux/rpmsg/qcom_smd.h> +#include <linux/soc/qcom/mdt_loader.h> #include "remoteproc_internal.h" #include "qcom_common.h" @@ -41,7 +42,7 @@ static int glink_subdev_probe(struct rproc_subdev *subdev) return PTR_ERR_OR_ZERO(glink->edge); } -static void glink_subdev_remove(struct rproc_subdev *subdev) +static void glink_subdev_remove(struct rproc_subdev *subdev, bool crashed) { struct qcom_rproc_glink *glink = to_glink_subdev(subdev); @@ -74,11 +75,57 @@ EXPORT_SYMBOL_GPL(qcom_add_glink_subdev); */ void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink) { + if (!glink->node) + return; + rproc_remove_subdev(rproc, &glink->subdev); of_node_put(glink->node); } EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev); +/** + * qcom_register_dump_segments() - register segments for coredump + * @rproc: remoteproc handle + * @fw: firmware header + * + * Register all segments of the ELF in the remoteproc coredump segment list + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_register_dump_segments(struct rproc *rproc, + const struct firmware *fw) +{ + const struct elf32_phdr *phdrs; + const struct elf32_phdr *phdr; + const struct elf32_hdr *ehdr; + int ret; + int i; + + ehdr = (struct elf32_hdr *)fw->data; + phdrs = (struct elf32_phdr *)(ehdr + 1); + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &phdrs[i]; + + if (phdr->p_type != PT_LOAD) + continue; + + if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) + continue; + + if (!phdr->p_memsz) + continue; + + ret = rproc_coredump_add_segment(rproc, phdr->p_paddr, + phdr->p_memsz); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_register_dump_segments); + static int smd_subdev_probe(struct rproc_subdev *subdev) { struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); @@ -88,7 +135,7 @@ static int smd_subdev_probe(struct rproc_subdev *subdev) return PTR_ERR_OR_ZERO(smd->edge); } -static void smd_subdev_remove(struct rproc_subdev *subdev) +static void smd_subdev_remove(struct rproc_subdev *subdev, bool crashed) { struct qcom_rproc_subdev *smd = to_smd_subdev(subdev); @@ -121,6 +168,9 @@ EXPORT_SYMBOL_GPL(qcom_add_smd_subdev); */ void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd) { + if (!smd->node) + return; + rproc_remove_subdev(rproc, &smd->subdev); of_node_put(smd->node); } @@ -157,7 +207,7 @@ static int ssr_notify_start(struct rproc_subdev *subdev) return 0; } -static void ssr_notify_stop(struct rproc_subdev *subdev) +static void ssr_notify_stop(struct rproc_subdev *subdev, bool crashed) { struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev); diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h index 728be98..58de71e 100644 --- a/drivers/remoteproc/qcom_common.h +++ b/drivers/remoteproc/qcom_common.h @@ -4,6 +4,9 @@ #include <linux/remoteproc.h> #include "remoteproc_internal.h" +#include <linux/soc/qcom/qmi.h> + +struct qcom_sysmon; struct qcom_rproc_glink { struct rproc_subdev subdev; @@ -30,6 +33,8 @@ struct qcom_rproc_ssr { void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink); +int qcom_register_dump_segments(struct rproc *rproc, const struct firmware *fw); + void qcom_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd); @@ -37,4 +42,22 @@ void qcom_add_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr, const char *ssr_name); void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr); +#if IS_ENABLED(CONFIG_QCOM_SYSMON) +struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, + const char *name, + int ssctl_instance); +void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon); +#else +static inline struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, + const char *name, + int ssctl_instance) +{ + return NULL; +} + +static inline void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon) +{ +} +#endif + #endif diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index b4e5e72..8e70a62 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c @@ -168,6 +168,7 @@ struct q6v5 { struct qcom_rproc_subdev smd_subdev; struct qcom_rproc_ssr ssr_subdev; + struct qcom_sysmon *sysmon; bool need_mem_protection; int mpss_perm; int mba_perm; @@ -939,9 +940,6 @@ static irqreturn_t q6v5_wdog_interrupt(int irq, void *dev) rproc_report_crash(qproc->rproc, RPROC_WATCHDOG); - if (!IS_ERR(msg)) - msg[0] = '\0'; - return IRQ_HANDLED; } @@ -959,9 +957,6 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *dev) rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR); - if (!IS_ERR(msg)) - msg[0] = '\0'; - return IRQ_HANDLED; } @@ -1215,6 +1210,7 @@ static int q6v5_probe(struct platform_device *pdev) qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); qcom_add_smd_subdev(rproc, &qproc->smd_subdev); qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); + qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12); ret = rproc_add(rproc); if (ret) @@ -1234,6 +1230,7 @@ static int q6v5_remove(struct platform_device *pdev) rproc_del(qproc->rproc); + qcom_remove_sysmon_subdev(qproc->sysmon); qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); rproc_free(qproc->rproc); diff --git a/drivers/remoteproc/qcom_sysmon.c b/drivers/remoteproc/qcom_sysmon.c new file mode 100644 index 0000000..f085545 --- /dev/null +++ b/drivers/remoteproc/qcom_sysmon.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017, Linaro Ltd. + */ +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/notifier.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/remoteproc/qcom_rproc.h> +#include <linux/rpmsg.h> + +#include "qcom_common.h" + +static BLOCKING_NOTIFIER_HEAD(sysmon_notifiers); + +struct qcom_sysmon { + struct rproc_subdev subdev; + struct rproc *rproc; + + struct list_head node; + + const char *name; + + int ssctl_version; + int ssctl_instance; + + struct notifier_block nb; + + struct device *dev; + + struct rpmsg_endpoint *ept; + struct completion comp; + struct mutex lock; + + bool ssr_ack; + + struct qmi_handle qmi; + struct sockaddr_qrtr ssctl; +}; + +static DEFINE_MUTEX(sysmon_lock); +static LIST_HEAD(sysmon_list); + +/** + * sysmon_send_event() - send notification of other remote's SSR event + * @sysmon: sysmon context + * @name: other remote's name + */ +static void sysmon_send_event(struct qcom_sysmon *sysmon, const char *name) +{ + char req[50]; + int len; + int ret; + + len = snprintf(req, sizeof(req), "ssr:%s:before_shutdown", name); + if (len >= sizeof(req)) + return; + + mutex_lock(&sysmon->lock); + reinit_completion(&sysmon->comp); + sysmon->ssr_ack = false; + + ret = rpmsg_send(sysmon->ept, req, len); + if (ret < 0) { + dev_err(sysmon->dev, "failed to send sysmon event\n"); + goto out_unlock; + } + + ret = wait_for_completion_timeout(&sysmon->comp, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(sysmon->dev, "timeout waiting for sysmon ack\n"); + goto out_unlock; + } + + if (!sysmon->ssr_ack) + dev_err(sysmon->dev, "unexpected response to sysmon event\n"); + +out_unlock: + mutex_unlock(&sysmon->lock); +} + +/** + * sysmon_request_shutdown() - request graceful shutdown of remote + * @sysmon: sysmon context + */ +static void sysmon_request_shutdown(struct qcom_sysmon *sysmon) +{ + char *req = "ssr:shutdown"; + int ret; + + mutex_lock(&sysmon->lock); + reinit_completion(&sysmon->comp); + sysmon->ssr_ack = false; + + ret = rpmsg_send(sysmon->ept, req, strlen(req) + 1); + if (ret < 0) { + dev_err(sysmon->dev, "send sysmon shutdown request failed\n"); + goto out_unlock; + } + + ret = wait_for_completion_timeout(&sysmon->comp, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(sysmon->dev, "timeout waiting for sysmon ack\n"); + goto out_unlock; + } + + if (!sysmon->ssr_ack) + dev_err(sysmon->dev, + "unexpected response to sysmon shutdown request\n"); + +out_unlock: + mutex_unlock(&sysmon->lock); +} + +static int sysmon_callback(struct rpmsg_device *rpdev, void *data, int count, + void *priv, u32 addr) +{ + struct qcom_sysmon *sysmon = priv; + const char *ssr_ack = "ssr:ack"; + const int ssr_ack_len = strlen(ssr_ack) + 1; + + if (!sysmon) + return -EINVAL; + + if (count >= ssr_ack_len && !memcmp(data, ssr_ack, ssr_ack_len)) + sysmon->ssr_ack = true; + + complete(&sysmon->comp); + + return 0; +} + +#define SSCTL_SHUTDOWN_REQ 0x21 +#define SSCTL_SUBSYS_EVENT_REQ 0x23 + +#define SSCTL_MAX_MSG_LEN 7 + +#define SSCTL_SUBSYS_NAME_LENGTH 15 + +enum { + SSCTL_SSR_EVENT_BEFORE_POWERUP, + SSCTL_SSR_EVENT_AFTER_POWERUP, + SSCTL_SSR_EVENT_BEFORE_SHUTDOWN, + SSCTL_SSR_EVENT_AFTER_SHUTDOWN, +}; + +enum { + SSCTL_SSR_EVENT_FORCED, + SSCTL_SSR_EVENT_GRACEFUL, +}; + +struct ssctl_shutdown_resp { + struct qmi_response_type_v01 resp; +}; + +static struct qmi_elem_info ssctl_shutdown_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ssctl_shutdown_resp, resp), + .ei_array = qmi_response_type_v01_ei, + }, + {} +}; + +struct ssctl_subsys_event_req { + u8 subsys_name_len; + char subsys_name[SSCTL_SUBSYS_NAME_LENGTH]; + u32 event; + u8 evt_driven_valid; + u32 evt_driven; +}; + +static struct qmi_elem_info ssctl_subsys_event_req_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct ssctl_subsys_event_req, + subsys_name_len), + .ei_array = NULL, + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = SSCTL_SUBSYS_NAME_LENGTH, + .elem_size = sizeof(char), + .array_type = VAR_LEN_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct ssctl_subsys_event_req, + subsys_name), + .ei_array = NULL, + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ssctl_subsys_event_req, + event), + .ei_array = NULL, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(uint8_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ssctl_subsys_event_req, + evt_driven_valid), + .ei_array = NULL, + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(uint32_t), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct ssctl_subsys_event_req, + evt_driven), + .ei_array = NULL, + }, + {} +}; + +struct ssctl_subsys_event_resp { + struct qmi_response_type_v01 resp; +}; + +static struct qmi_elem_info ssctl_subsys_event_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct ssctl_subsys_event_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + {} +}; + +/** + * ssctl_request_shutdown() - request shutdown via SSCTL QMI service + * @sysmon: sysmon context + */ +static void ssctl_request_shutdown(struct qcom_sysmon *sysmon) +{ + struct ssctl_shutdown_resp resp; + struct qmi_txn txn; + int ret; + + ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_shutdown_resp_ei, &resp); + if (ret < 0) { + dev_err(sysmon->dev, "failed to allocate QMI txn\n"); + return; + } + + ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn, + SSCTL_SHUTDOWN_REQ, 0, NULL, NULL); + if (ret < 0) { + dev_err(sysmon->dev, "failed to send shutdown request\n"); + qmi_txn_cancel(&txn); + return; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) + dev_err(sysmon->dev, "failed receiving QMI response\n"); + else if (resp.resp.result) + dev_err(sysmon->dev, "shutdown request failed\n"); + else + dev_dbg(sysmon->dev, "shutdown request completed\n"); +} + +/** + * ssctl_send_event() - send notification of other remote's SSR event + * @sysmon: sysmon context + * @name: other remote's name + */ +static void ssctl_send_event(struct qcom_sysmon *sysmon, const char *name) +{ + struct ssctl_subsys_event_resp resp; + struct ssctl_subsys_event_req req; + struct qmi_txn txn; + int ret; + + memset(&resp, 0, sizeof(resp)); + ret = qmi_txn_init(&sysmon->qmi, &txn, ssctl_subsys_event_resp_ei, &resp); + if (ret < 0) { + dev_err(sysmon->dev, "failed to allocate QMI txn\n"); + return; + } + + memset(&req, 0, sizeof(req)); + strlcpy(req.subsys_name, name, sizeof(req.subsys_name)); + req.subsys_name_len = strlen(req.subsys_name); + req.event = SSCTL_SSR_EVENT_BEFORE_SHUTDOWN; + req.evt_driven_valid = true; + req.evt_driven = SSCTL_SSR_EVENT_FORCED; + + ret = qmi_send_request(&sysmon->qmi, &sysmon->ssctl, &txn, + SSCTL_SUBSYS_EVENT_REQ, 40, + ssctl_subsys_event_req_ei, &req); + if (ret < 0) { + dev_err(sysmon->dev, "failed to send shutdown request\n"); + qmi_txn_cancel(&txn); + return; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) + dev_err(sysmon->dev, "failed receiving QMI response\n"); + else if (resp.resp.result) + dev_err(sysmon->dev, "ssr event send failed\n"); + else + dev_dbg(sysmon->dev, "ssr event send completed\n"); +} + +/** + * ssctl_new_server() - QMI callback indicating a new service + * @qmi: QMI handle + * @svc: service information + * + * Return: 0 if we're interested in this service, -EINVAL otherwise. + */ +static int ssctl_new_server(struct qmi_handle *qmi, struct qmi_service *svc) +{ + struct qcom_sysmon *sysmon = container_of(qmi, struct qcom_sysmon, qmi); + + switch (svc->version) { + case 1: + if (svc->instance != 0) + return -EINVAL; + if (strcmp(sysmon->name, "modem")) + return -EINVAL; + break; + case 2: + if (svc->instance != sysmon->ssctl_instance) + return -EINVAL; + break; + default: + return -EINVAL; + }; + + sysmon->ssctl_version = svc->version; + + sysmon->ssctl.sq_family = AF_QIPCRTR; + sysmon->ssctl.sq_node = svc->node; + sysmon->ssctl.sq_port = svc->port; + + svc->priv = sysmon; + + return 0; +} + +/** + * ssctl_del_server() - QMI callback indicating that @svc is removed + * @qmi: QMI handle + * @svc: service information + */ +static void ssctl_del_server(struct qmi_handle *qmi, struct qmi_service *svc) +{ + struct qcom_sysmon *sysmon = svc->priv; + + sysmon->ssctl_version = 0; +} + +static const struct qmi_ops ssctl_ops = { + .new_server = ssctl_new_server, + .del_server = ssctl_del_server, +}; + +static int sysmon_start(struct rproc_subdev *subdev) +{ + return 0; +} + +static void sysmon_stop(struct rproc_subdev *subdev, bool crashed) +{ + struct qcom_sysmon *sysmon = container_of(subdev, struct qcom_sysmon, subdev); + + blocking_notifier_call_chain(&sysmon_notifiers, 0, (void *)sysmon->name); + + /* Don't request graceful shutdown if we've crashed */ + if (crashed) + return; + + if (sysmon->ssctl_version) + ssctl_request_shutdown(sysmon); + else if (sysmon->ept) + sysmon_request_shutdown(sysmon); +} + +/** + * sysmon_notify() - notify sysmon target of another's SSR + * @nb: notifier_block associated with sysmon instance + * @event: unused + * @data: SSR identifier of the remote that is going down + */ +static int sysmon_notify(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct qcom_sysmon *sysmon = container_of(nb, struct qcom_sysmon, nb); + struct rproc *rproc = sysmon->rproc; + const char *ssr_name = data; + + /* Skip non-running rprocs and the originating instance */ + if (rproc->state != RPROC_RUNNING || !strcmp(data, sysmon->name)) { + dev_dbg(sysmon->dev, "not notifying %s\n", sysmon->name); + return NOTIFY_DONE; + } + + /* Only SSCTL version 2 supports SSR events */ + if (sysmon->ssctl_version == 2) + ssctl_send_event(sysmon, ssr_name); + else if (sysmon->ept) + sysmon_send_event(sysmon, ssr_name); + + return NOTIFY_DONE; +} + +/** + * qcom_add_sysmon_subdev() - create a sysmon subdev for the given remoteproc + * @rproc: rproc context to associate the subdev with + * @name: name of this subdev, to use in SSR + * @ssctl_instance: instance id of the ssctl QMI service + * + * Return: A new qcom_sysmon object, or NULL on failure + */ +struct qcom_sysmon *qcom_add_sysmon_subdev(struct rproc *rproc, + const char *name, + int ssctl_instance) +{ + struct qcom_sysmon *sysmon; + int ret; + + sysmon = kzalloc(sizeof(*sysmon), GFP_KERNEL); + if (!sysmon) + return NULL; + + sysmon->dev = rproc->dev.parent; + sysmon->rproc = rproc; + + sysmon->name = name; + sysmon->ssctl_instance = ssctl_instance; + + init_completion(&sysmon->comp); + mutex_init(&sysmon->lock); + + ret = qmi_handle_init(&sysmon->qmi, SSCTL_MAX_MSG_LEN, &ssctl_ops, NULL); + if (ret < 0) { + dev_err(sysmon->dev, "failed to initialize qmi handle\n"); + kfree(sysmon); + return NULL; + } + + qmi_add_lookup(&sysmon->qmi, 43, 0, 0); + + rproc_add_subdev(rproc, &sysmon->subdev, sysmon_start, sysmon_stop); + + sysmon->nb.notifier_call = sysmon_notify; + blocking_notifier_chain_register(&sysmon_notifiers, &sysmon->nb); + + mutex_lock(&sysmon_lock); + list_add(&sysmon->node, &sysmon_list); + mutex_unlock(&sysmon_lock); + + return sysmon; +} +EXPORT_SYMBOL_GPL(qcom_add_sysmon_subdev); + +/** + * qcom_remove_sysmon_subdev() - release a qcom_sysmon + * @sysmon: sysmon context, as retrieved by qcom_add_sysmon_subdev() + */ +void qcom_remove_sysmon_subdev(struct qcom_sysmon *sysmon) +{ + if (!sysmon) + return; + + mutex_lock(&sysmon_lock); + list_del(&sysmon->node); + mutex_unlock(&sysmon_lock); + + blocking_notifier_chain_unregister(&sysmon_notifiers, &sysmon->nb); + + rproc_remove_subdev(sysmon->rproc, &sysmon->subdev); + + qmi_handle_release(&sysmon->qmi); + + kfree(sysmon); +} +EXPORT_SYMBOL_GPL(qcom_remove_sysmon_subdev); + +/** + * sysmon_probe() - probe sys_mon channel + * @rpdev: rpmsg device handle + * + * Find the sysmon context associated with the ancestor remoteproc and assign + * this rpmsg device with said sysmon context. + * + * Return: 0 on success, negative errno on failure. + */ +static int sysmon_probe(struct rpmsg_device *rpdev) +{ + struct qcom_sysmon *sysmon; + struct rproc *rproc; + + rproc = rproc_get_by_child(&rpdev->dev); + if (!rproc) { + dev_err(&rpdev->dev, "sysmon device not child of rproc\n"); + return -EINVAL; + } + + mutex_lock(&sysmon_lock); + list_for_each_entry(sysmon, &sysmon_list, node) { + if (sysmon->rproc == rproc) + goto found; + } + mutex_unlock(&sysmon_lock); + + dev_err(&rpdev->dev, "no sysmon associated with parent rproc\n"); + + return -EINVAL; + +found: + mutex_unlock(&sysmon_lock); + + rpdev->ept->priv = sysmon; + sysmon->ept = rpdev->ept; + + return 0; +} + +/** + * sysmon_remove() - sys_mon channel remove handler + * @rpdev: rpmsg device handle + * + * Disassociate the rpmsg device with the sysmon instance. + */ +static void sysmon_remove(struct rpmsg_device *rpdev) +{ + struct qcom_sysmon *sysmon = rpdev->ept->priv; + + sysmon->ept = NULL; +} + +static const struct rpmsg_device_id sysmon_match[] = { + { "sys_mon" }, + {} +}; + +static struct rpmsg_driver sysmon_driver = { + .probe = sysmon_probe, + .remove = sysmon_remove, + .callback = sysmon_callback, + .id_table = sysmon_match, + .drv = { + .name = "qcom_sysmon", + }, +}; + +module_rpmsg_driver(sysmon_driver); + +MODULE_DESCRIPTION("Qualcomm sysmon driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index 3f06092..b0e07e9 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c @@ -40,6 +40,7 @@ #define WCNSS_CRASH_REASON_SMEM 422 #define WCNSS_FIRMWARE_NAME "wcnss.mdt" #define WCNSS_PAS_ID 6 +#define WCNSS_SSCTL_ID 0x13 #define WCNSS_SPARE_NVBIN_DLND BIT(25) @@ -98,6 +99,7 @@ struct qcom_wcnss { size_t mem_size; struct qcom_rproc_subdev smd_subdev; + struct qcom_sysmon *sysmon; }; static const struct wcnss_data riva_data = { @@ -153,7 +155,8 @@ static int wcnss_load(struct rproc *rproc, const struct firmware *fw) struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv; return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID, - wcnss->mem_region, wcnss->mem_phys, wcnss->mem_size); + wcnss->mem_region, wcnss->mem_phys, + wcnss->mem_size, &wcnss->mem_reloc); } static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss) @@ -308,6 +311,7 @@ static const struct rproc_ops wcnss_ops = { .start = wcnss_start, .stop = wcnss_stop, .da_to_va = wcnss_da_to_va, + .parse_fw = qcom_register_dump_segments, .load = wcnss_load, }; @@ -332,9 +336,6 @@ static irqreturn_t wcnss_fatal_interrupt(int irq, void *dev) rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR); - if (!IS_ERR(msg)) - msg[0] = '\0'; - return IRQ_HANDLED; } @@ -551,6 +552,7 @@ static int wcnss_probe(struct platform_device *pdev) } qcom_add_smd_subdev(rproc, &wcnss->smd_subdev); + wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID); ret = rproc_add(rproc); if (ret) @@ -573,6 +575,7 @@ static int wcnss_remove(struct platform_device *pdev) qcom_smem_state_put(wcnss->state); rproc_del(wcnss->rproc); + qcom_remove_sysmon_subdev(wcnss->sysmon); qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev); rproc_free(wcnss->rproc); diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 4170dfb..6d9c583 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -33,6 +33,7 @@ #include <linux/firmware.h> #include <linux/string.h> #include <linux/debugfs.h> +#include <linux/devcoredump.h> #include <linux/remoteproc.h> #include <linux/iommu.h> #include <linux/idr.h> @@ -307,7 +308,7 @@ static int rproc_vdev_do_probe(struct rproc_subdev *subdev) return rproc_add_virtio_dev(rvdev, rvdev->id); } -static void rproc_vdev_do_remove(struct rproc_subdev *subdev) +static void rproc_vdev_do_remove(struct rproc_subdev *subdev, bool crashed) { struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); @@ -788,17 +789,31 @@ static int rproc_probe_subdevices(struct rproc *rproc) unroll_registration: list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node) - subdev->remove(subdev); + subdev->remove(subdev, true); return ret; } -static void rproc_remove_subdevices(struct rproc *rproc) +static void rproc_remove_subdevices(struct rproc *rproc, bool crashed) { struct rproc_subdev *subdev; list_for_each_entry_reverse(subdev, &rproc->subdevs, node) - subdev->remove(subdev); + subdev->remove(subdev, crashed); +} + +/** + * rproc_coredump_cleanup() - clean up dump_segments list + * @rproc: the remote processor handle + */ +static void rproc_coredump_cleanup(struct rproc *rproc) +{ + struct rproc_dump_segment *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &rproc->dump_segments, node) { + list_del(&entry->node); + kfree(entry); + } } /** @@ -848,6 +863,8 @@ static void rproc_resource_cleanup(struct rproc *rproc) /* clean up remote vdev entries */ list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) kref_put(&rvdev->refcount, rproc_vdev_release); + + rproc_coredump_cleanup(rproc); } 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) rproc->bootaddr = rproc_get_boot_addr(rproc, fw); - /* load resource table */ - ret = rproc_load_rsc_table(rproc, fw); + /* Load resource table, core dump segment list etc from the firmware */ + ret = rproc_parse_fw(rproc, fw); if (ret) goto disable_iommu; @@ -992,13 +1009,13 @@ static int rproc_trigger_auto_boot(struct rproc *rproc) return ret; } -static int rproc_stop(struct rproc *rproc) +static int rproc_stop(struct rproc *rproc, bool crashed) { struct device *dev = &rproc->dev; int ret; /* remove any subdevices for the remote processor */ - rproc_remove_subdevices(rproc); + rproc_remove_subdevices(rproc, crashed); /* the installed resource table is no longer accessible */ rproc->table_ptr = rproc->cached_table; @@ -1018,6 +1035,113 @@ static int rproc_stop(struct rproc *rproc) } /** + * rproc_coredump_add_segment() - add segment of device memory to coredump + * @rproc: handle of a remote processor + * @da: device address + * @size: size of segment + * + * Add device memory to the list of segments to be included in a coredump for + * the remoteproc. + * + * Return: 0 on success, negative errno on error. + */ +int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size) +{ + struct rproc_dump_segment *segment; + + segment = kzalloc(sizeof(*segment), GFP_KERNEL); + if (!segment) + return -ENOMEM; + + segment->da = da; + segment->size = size; + + list_add_tail(&segment->node, &rproc->dump_segments); + + return 0; +} +EXPORT_SYMBOL(rproc_coredump_add_segment); + +/** + * rproc_coredump() - perform coredump + * @rproc: rproc handle + * + * This function will generate an ELF header for the registered segments + * and create a devcoredump device associated with rproc. + */ +static void rproc_coredump(struct rproc *rproc) +{ + struct rproc_dump_segment *segment; + struct elf32_phdr *phdr; + struct elf32_hdr *ehdr; + size_t data_size; + size_t offset; + void *data; + void *ptr; + int phnum = 0; + + if (list_empty(&rproc->dump_segments)) + return; + + data_size = sizeof(*ehdr); + list_for_each_entry(segment, &rproc->dump_segments, node) { + data_size += sizeof(*phdr) + segment->size; + + phnum++; + } + + data = vmalloc(data_size); + if (!data) + return; + + ehdr = data; + + memset(ehdr, 0, sizeof(*ehdr)); + memcpy(ehdr->e_ident, ELFMAG, SELFMAG); + ehdr->e_ident[EI_CLASS] = ELFCLASS32; + ehdr->e_ident[EI_DATA] = ELFDATA2LSB; + ehdr->e_ident[EI_VERSION] = EV_CURRENT; + ehdr->e_ident[EI_OSABI] = ELFOSABI_NONE; + ehdr->e_type = ET_CORE; + ehdr->e_machine = EM_NONE; + ehdr->e_version = EV_CURRENT; + ehdr->e_entry = rproc->bootaddr; + ehdr->e_phoff = sizeof(*ehdr); + ehdr->e_ehsize = sizeof(*ehdr); + ehdr->e_phentsize = sizeof(*phdr); + ehdr->e_phnum = phnum; + + phdr = data + ehdr->e_phoff; + offset = ehdr->e_phoff + sizeof(*phdr) * ehdr->e_phnum; + list_for_each_entry(segment, &rproc->dump_segments, node) { + memset(phdr, 0, sizeof(*phdr)); + phdr->p_type = PT_LOAD; + phdr->p_offset = offset; + phdr->p_vaddr = segment->da; + phdr->p_paddr = segment->da; + phdr->p_filesz = segment->size; + phdr->p_memsz = segment->size; + phdr->p_flags = PF_R | PF_W | PF_X; + phdr->p_align = 0; + + ptr = rproc_da_to_va(rproc, segment->da, segment->size); + if (!ptr) { + dev_err(&rproc->dev, + "invalid coredump segment (%pad, %zu)\n", + &segment->da, segment->size); + memset(data + offset, 0xff, segment->size); + } else { + memcpy(data + offset, ptr, segment->size); + } + + offset += phdr->p_filesz; + phdr++; + } + + dev_coredumpv(&rproc->dev, data, data_size, GFP_KERNEL); +} + +/** * rproc_trigger_recovery() - recover a remoteproc * @rproc: the remote processor * @@ -1039,10 +1163,13 @@ int rproc_trigger_recovery(struct rproc *rproc) if (ret) return ret; - ret = rproc_stop(rproc); + ret = rproc_stop(rproc, false); if (ret) goto unlock_mutex; + /* generate coredump */ + rproc_coredump(rproc); + /* load firmware */ ret = request_firmware(&firmware_p, rproc->firmware, dev); if (ret < 0) { @@ -1189,7 +1316,7 @@ void rproc_shutdown(struct rproc *rproc) if (!atomic_dec_and_test(&rproc->power)) goto out; - ret = rproc_stop(rproc); + ret = rproc_stop(rproc, true); if (ret) { atomic_inc(&rproc->power); goto out; @@ -1428,7 +1555,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, /* Default to ELF loader if no load function is specified */ if (!rproc->ops->load) { rproc->ops->load = rproc_elf_load_segments; - rproc->ops->load_rsc_table = rproc_elf_load_rsc_table; + rproc->ops->parse_fw = rproc_elf_load_rsc_table; rproc->ops->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table; rproc->ops->sanity_check = rproc_elf_sanity_check; rproc->ops->get_boot_addr = rproc_elf_get_boot_addr; @@ -1443,6 +1570,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, INIT_LIST_HEAD(&rproc->traces); INIT_LIST_HEAD(&rproc->rvdevs); INIT_LIST_HEAD(&rproc->subdevs); + INIT_LIST_HEAD(&rproc->dump_segments); INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); @@ -1535,7 +1663,7 @@ EXPORT_SYMBOL(rproc_del); void rproc_add_subdev(struct rproc *rproc, struct rproc_subdev *subdev, int (*probe)(struct rproc_subdev *subdev), - void (*remove)(struct rproc_subdev *subdev)) + void (*remove)(struct rproc_subdev *subdev, bool crashed)) { subdev->probe = probe; subdev->remove = remove; diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 55a2950..7570beb 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) return -EINVAL; } -static inline int rproc_load_rsc_table(struct rproc *rproc, - const struct firmware *fw) +static inline int rproc_parse_fw(struct rproc *rproc, const struct firmware *fw) { - if (rproc->ops->load_rsc_table) - return rproc->ops->load_rsc_table(rproc, fw); + if (rproc->ops->parse_fw) + return rproc->ops->parse_fw(rproc, fw); return 0; } diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index a993d19..5c4535b 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -37,7 +37,7 @@ config QCOM_PM config QCOM_QMI_HELPERS tristate - depends on ARCH_QCOM + depends on ARCH_QCOM && NET help Helper library for handling QMI encoded messages. QMI encoded 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 08bd854..17b314d 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); * @mem_region: allocated memory region to load firmware into * @mem_phys: physical address of allocated memory region * @mem_size: size of the allocated memory region + * @reloc_base: adjusted physical address after relocation * * Returns 0 on success, negative errno otherwise. */ int qcom_mdt_load(struct device *dev, const struct firmware *fw, const char *firmware, int pas_id, void *mem_region, - phys_addr_t mem_phys, size_t mem_size) + phys_addr_t mem_phys, size_t mem_size, + phys_addr_t *reloc_base) { const struct elf32_phdr *phdrs; const struct elf32_phdr *phdr; @@ -192,6 +194,9 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw, memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); } + if (reloc_base) + *reloc_base = mem_reloc; + out: kfree(fw_name); diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 728d421..d09a9c7 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -344,7 +344,7 @@ struct rproc_ops { int (*stop)(struct rproc *rproc); void (*kick)(struct rproc *rproc, int vqid); void * (*da_to_va)(struct rproc *rproc, u64 da, int len); - int (*load_rsc_table)(struct rproc *rproc, const struct firmware *fw); + int (*parse_fw)(struct rproc *rproc, const struct firmware *fw); struct resource_table *(*find_loaded_rsc_table)( struct rproc *rproc, const struct firmware *fw); int (*load)(struct rproc *rproc, const struct firmware *fw); @@ -395,6 +395,21 @@ enum rproc_crash_type { }; /** + * struct rproc_dump_segment - segment info from ELF header + * @node: list node related to the rproc segment list + * @da: device address of the segment + * @size: size of the segment + */ +struct rproc_dump_segment { + struct list_head node; + + dma_addr_t da; + size_t size; + + loff_t offset; +}; + +/** * struct rproc - represents a physical remote processor device * @node: list node of this rproc object * @domain: iommu domain @@ -424,6 +439,7 @@ enum rproc_crash_type { * @cached_table: copy of the resource table * @table_sz: size of @cached_table * @has_iommu: flag to indicate if remote processor is behind an MMU + * @dump_segments: list of segments in the firmware */ struct rproc { struct list_head node; @@ -455,19 +471,21 @@ struct rproc { size_t table_sz; bool has_iommu; bool auto_boot; + struct list_head dump_segments; }; /** * struct rproc_subdev - subdevice tied to a remoteproc * @node: list node related to the rproc subdevs list * @probe: probe function, called as the rproc is started - * @remove: remove function, called as the rproc is stopped + * @remove: remove function, called as the rproc is being stopped, the @crashed + * parameter indicates if this originates from the a recovery */ struct rproc_subdev { struct list_head node; int (*probe)(struct rproc_subdev *subdev); - void (*remove)(struct rproc_subdev *subdev); + void (*remove)(struct rproc_subdev *subdev, bool crashed); }; /* we currently support only two vrings per rvdev */ @@ -534,6 +552,7 @@ void rproc_free(struct rproc *rproc); int rproc_boot(struct rproc *rproc); void rproc_shutdown(struct rproc *rproc); void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type); +int rproc_coredump_add_segment(struct rproc *rproc, dma_addr_t da, size_t size); static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev) { @@ -550,7 +569,7 @@ static inline struct rproc *vdev_to_rproc(struct virtio_device *vdev) void rproc_add_subdev(struct rproc *rproc, struct rproc_subdev *subdev, int (*probe)(struct rproc_subdev *subdev), - void (*remove)(struct rproc_subdev *subdev)); + void (*remove)(struct rproc_subdev *subdev, bool graceful)); void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev); diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index bd8e086..5b98bbd 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h @@ -14,6 +14,7 @@ struct firmware; ssize_t qcom_mdt_get_size(const struct firmware *fw); int qcom_mdt_load(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, void *mem_region, - phys_addr_t mem_phys, size_t mem_size); + phys_addr_t mem_phys, size_t mem_size, + phys_addr_t *reloc_base); #endif diff --git a/samples/Kconfig b/samples/Kconfig index f524f55..3db002b 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -62,6 +62,16 @@ config SAMPLE_KDB Build an example of how to dynamically add the hello command to the kdb shell. +config SAMPLE_QMI_CLIENT + tristate "Build qmi client sample -- loadable modules only" + depends on m + depends on ARCH_QCOM + depends on NET + select QCOM_QMI_HELPERS + help + Build an QMI client sample driver, which demonstrates how to + communicate with a remote QRTR service, using QMI encoded messages. + config SAMPLE_RPMSG_CLIENT tristate "Build rpmsg client sample -- loadable modules only" depends on RPMSG && m diff --git a/samples/Makefile b/samples/Makefile index 70cf375..bd601c0 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -3,4 +3,4 @@ obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ configfs/ connector/ v4l/ trace_printk/ \ - vfio-mdev/ statx/ + vfio-mdev/ statx/ qmi/ diff --git a/samples/qmi/Makefile b/samples/qmi/Makefile new file mode 100644 index 0000000..2b111d2 --- /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 0000000..c9e7276 --- /dev/null +++ b/samples/qmi/qmi_sample_client.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sample in-kernel QMI client driver + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/qrtr.h> +#include <linux/net.h> +#include <linux/completion.h> +#include <linux/idr.h> +#include <linux/string.h> +#include <net/sock.h> +#include <linux/soc/qcom/qmi.h> + +#define PING_REQ1_TLV_TYPE 0x1 +#define PING_RESP1_TLV_TYPE 0x2 +#define PING_OPT1_TLV_TYPE 0x10 +#define PING_OPT2_TLV_TYPE 0x11 + +#define DATA_REQ1_TLV_TYPE 0x1 +#define DATA_RESP1_TLV_TYPE 0x2 +#define DATA_OPT1_TLV_TYPE 0x10 +#define DATA_OPT2_TLV_TYPE 0x11 + +#define TEST_MED_DATA_SIZE_V01 8192 +#define TEST_MAX_NAME_SIZE_V01 255 + +#define TEST_PING_REQ_MSG_ID_V01 0x20 +#define TEST_DATA_REQ_MSG_ID_V01 0x21 + +#define TEST_PING_REQ_MAX_MSG_LEN_V01 266 +#define TEST_DATA_REQ_MAX_MSG_LEN_V01 8456 + +struct test_name_type_v01 { + u32 name_len; + char name[TEST_MAX_NAME_SIZE_V01]; +}; + +static struct qmi_elem_info test_name_type_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + .offset = offsetof(struct test_name_type_v01, + name_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = TEST_MAX_NAME_SIZE_V01, + .elem_size = sizeof(char), + .array_type = VAR_LEN_ARRAY, + .tlv_type = QMI_COMMON_TLV_TYPE, + .offset = offsetof(struct test_name_type_v01, + name), + }, + {} +}; + +struct test_ping_req_msg_v01 { + char ping[4]; + + u8 client_name_valid; + struct test_name_type_v01 client_name; +}; + +static struct qmi_elem_info test_ping_req_msg_v01_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 4, + .elem_size = sizeof(char), + .array_type = STATIC_ARRAY, + .tlv_type = PING_REQ1_TLV_TYPE, + .offset = offsetof(struct test_ping_req_msg_v01, + ping), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = PING_OPT1_TLV_TYPE, + .offset = offsetof(struct test_ping_req_msg_v01, + client_name_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct test_name_type_v01), + .array_type = NO_ARRAY, + .tlv_type = PING_OPT1_TLV_TYPE, + .offset = offsetof(struct test_ping_req_msg_v01, + client_name), + .ei_array = test_name_type_v01_ei, + }, + {} +}; + +struct test_ping_resp_msg_v01 { + struct qmi_response_type_v01 resp; + + u8 pong_valid; + char pong[4]; + + u8 service_name_valid; + struct test_name_type_v01 service_name; +}; + +static struct qmi_elem_info test_ping_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = PING_RESP1_TLV_TYPE, + .offset = offsetof(struct test_ping_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = PING_OPT1_TLV_TYPE, + .offset = offsetof(struct test_ping_resp_msg_v01, + pong_valid), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 4, + .elem_size = sizeof(char), + .array_type = STATIC_ARRAY, + .tlv_type = PING_OPT1_TLV_TYPE, + .offset = offsetof(struct test_ping_resp_msg_v01, + pong), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = PING_OPT2_TLV_TYPE, + .offset = offsetof(struct test_ping_resp_msg_v01, + service_name_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct test_name_type_v01), + .array_type = NO_ARRAY, + .tlv_type = PING_OPT2_TLV_TYPE, + .offset = offsetof(struct test_ping_resp_msg_v01, + service_name), + .ei_array = test_name_type_v01_ei, + }, + {} +}; + +struct test_data_req_msg_v01 { + u32 data_len; + u8 data[TEST_MED_DATA_SIZE_V01]; + + u8 client_name_valid; + struct test_name_type_v01 client_name; +}; + +static struct qmi_elem_info test_data_req_msg_v01_ei[] = { + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = DATA_REQ1_TLV_TYPE, + .offset = offsetof(struct test_data_req_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = TEST_MED_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .array_type = VAR_LEN_ARRAY, + .tlv_type = DATA_REQ1_TLV_TYPE, + .offset = offsetof(struct test_data_req_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT1_TLV_TYPE, + .offset = offsetof(struct test_data_req_msg_v01, + client_name_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct test_name_type_v01), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT1_TLV_TYPE, + .offset = offsetof(struct test_data_req_msg_v01, + client_name), + .ei_array = test_name_type_v01_ei, + }, + {} +}; + +struct test_data_resp_msg_v01 { + struct qmi_response_type_v01 resp; + + u8 data_valid; + u32 data_len; + u8 data[TEST_MED_DATA_SIZE_V01]; + + u8 service_name_valid; + struct test_name_type_v01 service_name; +}; + +static struct qmi_elem_info test_data_resp_msg_v01_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = DATA_RESP1_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT1_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + data_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT1_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + data_len), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = TEST_MED_DATA_SIZE_V01, + .elem_size = sizeof(u8), + .array_type = VAR_LEN_ARRAY, + .tlv_type = DATA_OPT1_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + data), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT2_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + service_name_valid), + }, + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct test_name_type_v01), + .array_type = NO_ARRAY, + .tlv_type = DATA_OPT2_TLV_TYPE, + .offset = offsetof(struct test_data_resp_msg_v01, + service_name), + .ei_array = test_name_type_v01_ei, + }, + {} +}; + +/* + * ping_write() - ping_pong debugfs file write handler + * @file: debugfs file context + * @user_buf: reference to the user data (ignored) + * @count: number of bytes in @user_buf + * @ppos: offset in @file to write + * + * This function allows user space to send out a ping_pong QMI encoded message + * to the associated remote test service and will return with the result of the + * transaction. It serves as an example of how to provide a custom response + * handler. + * + * Return: @count, or negative errno on failure. + */ +static ssize_t ping_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct qmi_handle *qmi = file->private_data; + struct test_ping_req_msg_v01 req = {}; + struct qmi_txn txn; + int ret; + + memcpy(req.ping, "ping", sizeof(req.ping)); + + ret = qmi_txn_init(qmi, &txn, NULL, NULL); + if (ret < 0) + return ret; + + ret = qmi_send_request(qmi, NULL, &txn, + TEST_PING_REQ_MSG_ID_V01, + TEST_PING_REQ_MAX_MSG_LEN_V01, + test_ping_req_msg_v01_ei, &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + return ret; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) + count = ret; + + return count; +} + +static const struct file_operations ping_fops = { + .open = simple_open, + .write = ping_write, +}; + +static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + const struct test_ping_resp_msg_v01 *resp = data; + + if (!txn) { + pr_err("spurious ping response\n"); + return; + } + + if (resp->resp.result == QMI_RESULT_FAILURE_V01) + txn->result = -ENXIO; + else if (!resp->pong_valid || memcmp(resp->pong, "pong", 4)) + txn->result = -EINVAL; + + complete(&txn->completion); +} + +/* + * data_write() - data debugfs file write handler + * @file: debugfs file context + * @user_buf: reference to the user data + * @count: number of bytes in @user_buf + * @ppos: offset in @file to write + * + * This function allows user space to send out a data QMI encoded message to + * the associated remote test service and will return with the result of the + * transaction. It serves as an example of how to have the QMI helpers decode a + * transaction response into a provided object automatically. + * + * Return: @count, or negative errno on failure. + */ +static ssize_t data_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) + +{ + struct qmi_handle *qmi = file->private_data; + struct test_data_resp_msg_v01 *resp; + struct test_data_req_msg_v01 *req; + struct qmi_txn txn; + int ret; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) { + kfree(req); + return -ENOMEM; + } + + req->data_len = min_t(size_t, sizeof(req->data), count); + if (copy_from_user(req->data, user_buf, req->data_len)) { + ret = -EFAULT; + goto out; + } + + ret = qmi_txn_init(qmi, &txn, test_data_resp_msg_v01_ei, resp); + if (ret < 0) + goto out; + + ret = qmi_send_request(qmi, NULL, &txn, + TEST_DATA_REQ_MSG_ID_V01, + TEST_DATA_REQ_MAX_MSG_LEN_V01, + test_data_req_msg_v01_ei, req); + if (ret < 0) { + qmi_txn_cancel(&txn); + goto out; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) { + goto out; + } else if (!resp->data_valid || + resp->data_len != req->data_len || + memcmp(resp->data, req->data, req->data_len)) { + pr_err("response data doesn't match expectation\n"); + ret = -EINVAL; + goto out; + } + + ret = count; + +out: + kfree(resp); + kfree(req); + + return ret; +} + +static const struct file_operations data_fops = { + .open = simple_open, + .write = data_write, +}; + +static struct qmi_msg_handler qmi_sample_handlers[] = { + { + .type = QMI_RESPONSE, + .msg_id = TEST_PING_REQ_MSG_ID_V01, + .ei = test_ping_resp_msg_v01_ei, + .decoded_size = sizeof(struct test_ping_req_msg_v01), + .fn = ping_pong_cb + }, + {} +}; + +struct qmi_sample { + struct qmi_handle qmi; + + struct dentry *de_dir; + struct dentry *de_data; + struct dentry *de_ping; +}; + +static struct dentry *qmi_debug_dir; + +static int qmi_sample_probe(struct platform_device *pdev) +{ + struct sockaddr_qrtr *sq; + struct qmi_sample *sample; + char path[20]; + int ret; + + sample = devm_kzalloc(&pdev->dev, sizeof(*sample), GFP_KERNEL); + if (!sample) + return -ENOMEM; + + ret = qmi_handle_init(&sample->qmi, TEST_DATA_REQ_MAX_MSG_LEN_V01, + NULL, + qmi_sample_handlers); + if (ret < 0) + return ret; + + sq = dev_get_platdata(&pdev->dev); + ret = kernel_connect(sample->qmi.sock, (struct sockaddr *)sq, + sizeof(*sq), 0); + if (ret < 0) { + pr_err("failed to connect to remote service port\n"); + goto err_release_qmi_handle; + } + + snprintf(path, sizeof(path), "%d:%d", sq->sq_node, sq->sq_port); + + sample->de_dir = debugfs_create_dir(path, qmi_debug_dir); + if (IS_ERR(sample->de_dir)) { + ret = PTR_ERR(sample->de_dir); + goto err_release_qmi_handle; + } + + sample->de_data = debugfs_create_file("data", 0600, sample->de_dir, + sample, &data_fops); + if (IS_ERR(sample->de_data)) { + ret = PTR_ERR(sample->de_data); + goto err_remove_de_dir; + } + + sample->de_ping = debugfs_create_file("ping", 0600, sample->de_dir, + sample, &ping_fops); + if (IS_ERR(sample->de_ping)) { + ret = PTR_ERR(sample->de_ping); + goto err_remove_de_data; + } + + platform_set_drvdata(pdev, sample); + + return 0; + +err_remove_de_data: + debugfs_remove(sample->de_data); +err_remove_de_dir: + debugfs_remove(sample->de_dir); +err_release_qmi_handle: + qmi_handle_release(&sample->qmi); + + return ret; +} + +static int qmi_sample_remove(struct platform_device *pdev) +{ + struct qmi_sample *sample = platform_get_drvdata(pdev); + + debugfs_remove(sample->de_ping); + debugfs_remove(sample->de_data); + debugfs_remove(sample->de_dir); + + qmi_handle_release(&sample->qmi); + + return 0; +} + +static struct platform_driver qmi_sample_driver = { + .probe = qmi_sample_probe, + .remove = qmi_sample_remove, + .driver = { + .name = "qmi_sample_client", + }, +}; + +static int qmi_sample_new_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct platform_device *pdev; + struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port }; + int ret; + + pdev = platform_device_alloc("qmi_sample_client", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + + ret = platform_device_add_data(pdev, &sq, sizeof(sq)); + if (ret) + goto err_put_device; + + ret = platform_device_add(pdev); + if (ret) + goto err_put_device; + + service->priv = pdev; + + return 0; + +err_put_device: + platform_device_put(pdev); + + return ret; +} + +static void qmi_sample_del_server(struct qmi_handle *qmi, + struct qmi_service *service) +{ + struct platform_device *pdev = service->priv; + + platform_device_unregister(pdev); +} + +static struct qmi_handle lookup_client; + +static struct qmi_ops lookup_ops = { + .new_server = qmi_sample_new_server, + .del_server = qmi_sample_del_server, +}; + +static int qmi_sample_init(void) +{ + int ret; + + qmi_debug_dir = debugfs_create_dir("qmi_sample", NULL); + if (IS_ERR(qmi_debug_dir)) { + pr_err("failed to create qmi_sample dir\n"); + return PTR_ERR(qmi_debug_dir); + } + + ret = platform_driver_register(&qmi_sample_driver); + if (ret) + goto err_remove_debug_dir; + + ret = qmi_handle_init(&lookup_client, 0, &lookup_ops, NULL); + if (ret < 0) + goto err_unregister_driver; + + qmi_add_lookup(&lookup_client, 15, 0, 0); + + return 0; + +err_unregister_driver: + platform_driver_unregister(&qmi_sample_driver); +err_remove_debug_dir: + debugfs_remove(qmi_debug_dir); + + return ret; +} + +static void qmi_sample_exit(void) +{ + qmi_handle_release(&lookup_client); + + platform_driver_unregister(&qmi_sample_driver); + + debugfs_remove(qmi_debug_dir); +} + +module_init(qmi_sample_init); +module_exit(qmi_sample_exit); + +MODULE_DESCRIPTION("Sample QMI client driver"); +MODULE_LICENSE("GPL v2"); |