remoteproc updates for v4.17
This adds support for generating coredumps for remoteprocs using devcoredump, adds the Qualcomm sysmon driver for intra-remoteproc crash handling and a number of fixes in Qualcomm and IMX drivers. -----BEGIN PGP SIGNATURE----- iQJPBAABCAA5FiEEBd4DzF816k8JZtUlCx85Pw2ZrcUFAlrL5dgbHGJqb3JuLmFu ZGVyc3NvbkBsaW5hcm8ub3JnAAoJEAsfOT8Nma3FufUP/ifQ+QbDzYzBI4CHfbef hls8bplN9GlW4acJuhVRe8ikGz6vHZdyH88KJJX+IHekDXFqk10rWEgcyfV0h5mU qOrhiW7hjfm2Mhs0MvNg9rKxbFrD5wAeJBJn8ukTB4z4jc9iFPazVu37V3ul/wVR yMM4zI6kA54UwsLunqLEapb1thgLO4P99tvA+vdR8ekz/j0AVHB7HiHVLY1JX5Gu D679icQSWOSsSQaRR7G0E97ZKZF6XaM6+aJV3eZol1HQw8k3I/Vbudi2wIKj0Ia8 jLO5JyCpgeSAAjPCZLrB5XyUXl/vU7PSPKTPyOnv2lK1wQgKsmHX80HfeAt1ISNn 4nY/Oy651/rjBN3FR9HQvQoqCDF08GG5YRDmUZCg0kS/OStrbJT4eCm+hklvanmK O98LEWm5+FLqk+9Wau1DXGa0raXon2REIJxQSDnBUODZSqTQBS1iQmdtdYtrZxRp 17LtCABCSBMbAI8VE6eb/5jj/z07aI0pBwKEYEWOOG2xsF6q1GGZAXRxnmMUOWAv bwSgiNqNbbYUdYMjVYDxDtWIp3/Dy8VQ3N3Fz61SieS0GjJxDy/SyNByzVL4eEjk yYj+N74Dxgms1V/xknQmJn4ktwqaVxapnj52WF1WaEtIcm5WvPFZUVNSFvWMlbcD rAiTKkpFC0CkINlM+dk3Ti/a =cxV7 -----END PGP SIGNATURE----- Merge tag 'rproc-v4.17' of git://github.com/andersson/remoteproc Pull remoteproc updates from Bjorn Andersson: - add support for generating coredumps for remoteprocs using devcoredump - add the Qualcomm sysmon driver for intra-remoteproc crash handling - a number of fixes in Qualcomm and IMX drivers * tag 'rproc-v4.17' of git://github.com/andersson/remoteproc: remoteproc: fix null pointer dereference on glink only platforms soc: qcom: qmi: add CONFIG_NET dependency remoteproc: imx_rproc: Slightly simplify code in 'imx_rproc_probe()' remoteproc: imx_rproc: Re-use existing error handling path in 'imx_rproc_probe()' remoteproc: imx_rproc: Fix an error handling path in 'imx_rproc_probe()' samples: Introduce Qualcomm QMI sample client remoteproc: qcom: Introduce sysmon remoteproc: Pass type of shutdown to subdev remove remoteproc: qcom: Register segments for core dump soc: qcom: mdt-loader: Return relocation base remoteproc: Rename "load_rsc_table" to "parse_fw" remoteproc: Add remote processor coredump support remoteproc: Remove null character write of shared mem
This commit is contained in:
commit
92589cbdda
|
@ -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) {
|
if (to_adreno_gpu(gpu)->fwloc == FW_LOCATION_LEGACY) {
|
||||||
ret = qcom_mdt_load(dev, fw, fwname, GPU_PAS_ID,
|
ret = qcom_mdt_load(dev, fw, fwname, GPU_PAS_ID,
|
||||||
mem_region, mem_phys, mem_size);
|
mem_region, mem_phys, mem_size, NULL);
|
||||||
} else {
|
} else {
|
||||||
char newname[strlen("qcom/") + strlen(fwname) + 1];
|
char newname[strlen("qcom/") + strlen(fwname) + 1];
|
||||||
|
|
||||||
sprintf(newname, "qcom/%s", fwname);
|
sprintf(newname, "qcom/%s", fwname);
|
||||||
|
|
||||||
ret = qcom_mdt_load(dev, fw, newname, GPU_PAS_ID,
|
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)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
|
@ -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,
|
ret = qcom_mdt_load(dev, mdt, fwname, VENUS_PAS_ID, mem_va, mem_phys,
|
||||||
mem_size);
|
mem_size, NULL);
|
||||||
|
|
||||||
release_firmware(mdt);
|
release_firmware(mdt);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ config REMOTEPROC
|
||||||
select CRC32
|
select CRC32
|
||||||
select FW_LOADER
|
select FW_LOADER
|
||||||
select VIRTIO
|
select VIRTIO
|
||||||
|
select WANT_DEV_COREDUMP
|
||||||
help
|
help
|
||||||
Support for remote processors (such as DSP coprocessors). These
|
Support for remote processors (such as DSP coprocessors). These
|
||||||
are mainly used on embedded systems.
|
are mainly used on embedded systems.
|
||||||
|
@ -90,6 +91,7 @@ config QCOM_ADSP_PIL
|
||||||
depends on QCOM_SMEM
|
depends on QCOM_SMEM
|
||||||
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
||||||
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
||||||
|
depends on QCOM_SYSMON || QCOM_SYSMON=n
|
||||||
select MFD_SYSCON
|
select MFD_SYSCON
|
||||||
select QCOM_MDT_LOADER
|
select QCOM_MDT_LOADER
|
||||||
select QCOM_RPROC_COMMON
|
select QCOM_RPROC_COMMON
|
||||||
|
@ -107,6 +109,7 @@ config QCOM_Q6V5_PIL
|
||||||
depends on QCOM_SMEM
|
depends on QCOM_SMEM
|
||||||
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
||||||
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
||||||
|
depends on QCOM_SYSMON || QCOM_SYSMON=n
|
||||||
select MFD_SYSCON
|
select MFD_SYSCON
|
||||||
select QCOM_RPROC_COMMON
|
select QCOM_RPROC_COMMON
|
||||||
select QCOM_SCM
|
select QCOM_SCM
|
||||||
|
@ -114,12 +117,28 @@ config QCOM_Q6V5_PIL
|
||||||
Say y here to support the Qualcomm Peripherial Image Loader for the
|
Say y here to support the Qualcomm Peripherial Image Loader for the
|
||||||
Hexagon V5 based remote processors.
|
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
|
config QCOM_WCNSS_PIL
|
||||||
tristate "Qualcomm WCNSS Peripheral Image Loader"
|
tristate "Qualcomm WCNSS Peripheral Image Loader"
|
||||||
depends on OF && ARCH_QCOM
|
depends on OF && ARCH_QCOM
|
||||||
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
|
||||||
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
|
||||||
depends on QCOM_SMEM
|
depends on QCOM_SMEM
|
||||||
|
depends on QCOM_SYSMON || QCOM_SYSMON=n
|
||||||
select QCOM_MDT_LOADER
|
select QCOM_MDT_LOADER
|
||||||
select QCOM_RPROC_COMMON
|
select QCOM_RPROC_COMMON
|
||||||
select QCOM_SCM
|
select QCOM_SCM
|
||||||
|
|
|
@ -17,6 +17,7 @@ obj-$(CONFIG_KEYSTONE_REMOTEPROC) += keystone_remoteproc.o
|
||||||
obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
|
obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o
|
||||||
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
|
obj-$(CONFIG_QCOM_RPROC_COMMON) += qcom_common.o
|
||||||
obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.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
|
obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o
|
||||||
qcom_wcnss_pil-y += qcom_wcnss.o
|
qcom_wcnss_pil-y += qcom_wcnss.o
|
||||||
qcom_wcnss_pil-y += qcom_wcnss_iris.o
|
qcom_wcnss_pil-y += qcom_wcnss_iris.o
|
||||||
|
|
|
@ -333,14 +333,14 @@ static int imx_rproc_probe(struct platform_device *pdev)
|
||||||
/* set some other name then imx */
|
/* set some other name then imx */
|
||||||
rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops,
|
rproc = rproc_alloc(dev, "imx-rproc", &imx_rproc_ops,
|
||||||
NULL, sizeof(*priv));
|
NULL, sizeof(*priv));
|
||||||
if (!rproc) {
|
if (!rproc)
|
||||||
ret = -ENOMEM;
|
return -ENOMEM;
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
dcfg = of_device_get_match_data(dev);
|
dcfg = of_device_get_match_data(dev);
|
||||||
if (!dcfg)
|
if (!dcfg) {
|
||||||
return -EINVAL;
|
ret = -EINVAL;
|
||||||
|
goto err_put_rproc;
|
||||||
|
}
|
||||||
|
|
||||||
priv = rproc->priv;
|
priv = rproc->priv;
|
||||||
priv->rproc = rproc;
|
priv->rproc = rproc;
|
||||||
|
@ -359,8 +359,8 @@ static int imx_rproc_probe(struct platform_device *pdev)
|
||||||
priv->clk = devm_clk_get(dev, NULL);
|
priv->clk = devm_clk_get(dev, NULL);
|
||||||
if (IS_ERR(priv->clk)) {
|
if (IS_ERR(priv->clk)) {
|
||||||
dev_err(dev, "Failed to get clock\n");
|
dev_err(dev, "Failed to get clock\n");
|
||||||
rproc_free(rproc);
|
ret = PTR_ERR(priv->clk);
|
||||||
return 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);
|
ret = clk_prepare_enable(priv->clk);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&rproc->dev, "Failed to enable clock\n");
|
dev_err(&rproc->dev, "Failed to enable clock\n");
|
||||||
rproc_free(rproc);
|
goto err_put_rproc;
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = rproc_add(rproc);
|
ret = rproc_add(rproc);
|
||||||
|
@ -380,13 +379,13 @@ static int imx_rproc_probe(struct platform_device *pdev)
|
||||||
goto err_put_clk;
|
goto err_put_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return 0;
|
||||||
|
|
||||||
err_put_clk:
|
err_put_clk:
|
||||||
clk_disable_unprepare(priv->clk);
|
clk_disable_unprepare(priv->clk);
|
||||||
err_put_rproc:
|
err_put_rproc:
|
||||||
rproc_free(rproc);
|
rproc_free(rproc);
|
||||||
err:
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,10 @@ struct adsp_data {
|
||||||
const char *firmware_name;
|
const char *firmware_name;
|
||||||
int pas_id;
|
int pas_id;
|
||||||
bool has_aggre2_clk;
|
bool has_aggre2_clk;
|
||||||
|
|
||||||
const char *ssr_name;
|
const char *ssr_name;
|
||||||
|
const char *sysmon_name;
|
||||||
|
int ssctl_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct qcom_adsp {
|
struct qcom_adsp {
|
||||||
|
@ -75,6 +78,7 @@ struct qcom_adsp {
|
||||||
struct qcom_rproc_glink glink_subdev;
|
struct qcom_rproc_glink glink_subdev;
|
||||||
struct qcom_rproc_subdev smd_subdev;
|
struct qcom_rproc_subdev smd_subdev;
|
||||||
struct qcom_rproc_ssr ssr_subdev;
|
struct qcom_rproc_ssr ssr_subdev;
|
||||||
|
struct qcom_sysmon *sysmon;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int adsp_load(struct rproc *rproc, const struct firmware *fw)
|
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;
|
struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv;
|
||||||
|
|
||||||
return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id,
|
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)
|
static int adsp_start(struct rproc *rproc)
|
||||||
|
@ -177,6 +183,7 @@ static const struct rproc_ops adsp_ops = {
|
||||||
.start = adsp_start,
|
.start = adsp_start,
|
||||||
.stop = adsp_stop,
|
.stop = adsp_stop,
|
||||||
.da_to_va = adsp_da_to_va,
|
.da_to_va = adsp_da_to_va,
|
||||||
|
.parse_fw = qcom_register_dump_segments,
|
||||||
.load = adsp_load,
|
.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);
|
rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR);
|
||||||
|
|
||||||
if (!IS_ERR(msg))
|
|
||||||
msg[0] = '\0';
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
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_glink_subdev(rproc, &adsp->glink_subdev);
|
||||||
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
|
qcom_add_smd_subdev(rproc, &adsp->smd_subdev);
|
||||||
qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name);
|
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);
|
ret = rproc_add(rproc);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -419,6 +426,7 @@ static int adsp_remove(struct platform_device *pdev)
|
||||||
rproc_del(adsp->rproc);
|
rproc_del(adsp->rproc);
|
||||||
|
|
||||||
qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev);
|
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_smd_subdev(adsp->rproc, &adsp->smd_subdev);
|
||||||
qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
|
qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev);
|
||||||
rproc_free(adsp->rproc);
|
rproc_free(adsp->rproc);
|
||||||
|
@ -432,6 +440,8 @@ static const struct adsp_data adsp_resource_init = {
|
||||||
.pas_id = 1,
|
.pas_id = 1,
|
||||||
.has_aggre2_clk = false,
|
.has_aggre2_clk = false,
|
||||||
.ssr_name = "lpass",
|
.ssr_name = "lpass",
|
||||||
|
.sysmon_name = "adsp",
|
||||||
|
.ssctl_id = 0x14,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct adsp_data slpi_resource_init = {
|
static const struct adsp_data slpi_resource_init = {
|
||||||
|
@ -440,6 +450,8 @@ static const struct adsp_data slpi_resource_init = {
|
||||||
.pas_id = 12,
|
.pas_id = 12,
|
||||||
.has_aggre2_clk = true,
|
.has_aggre2_clk = true,
|
||||||
.ssr_name = "dsps",
|
.ssr_name = "dsps",
|
||||||
|
.sysmon_name = "slpi",
|
||||||
|
.ssctl_id = 0x16,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct of_device_id adsp_of_match[] = {
|
static const struct of_device_id adsp_of_match[] = {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include <linux/remoteproc.h>
|
#include <linux/remoteproc.h>
|
||||||
#include <linux/rpmsg/qcom_glink.h>
|
#include <linux/rpmsg/qcom_glink.h>
|
||||||
#include <linux/rpmsg/qcom_smd.h>
|
#include <linux/rpmsg/qcom_smd.h>
|
||||||
|
#include <linux/soc/qcom/mdt_loader.h>
|
||||||
|
|
||||||
#include "remoteproc_internal.h"
|
#include "remoteproc_internal.h"
|
||||||
#include "qcom_common.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);
|
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);
|
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)
|
void qcom_remove_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink)
|
||||||
{
|
{
|
||||||
|
if (!glink->node)
|
||||||
|
return;
|
||||||
|
|
||||||
rproc_remove_subdev(rproc, &glink->subdev);
|
rproc_remove_subdev(rproc, &glink->subdev);
|
||||||
of_node_put(glink->node);
|
of_node_put(glink->node);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(qcom_remove_glink_subdev);
|
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)
|
static int smd_subdev_probe(struct rproc_subdev *subdev)
|
||||||
{
|
{
|
||||||
struct qcom_rproc_subdev *smd = to_smd_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);
|
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);
|
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)
|
void qcom_remove_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd)
|
||||||
{
|
{
|
||||||
|
if (!smd->node)
|
||||||
|
return;
|
||||||
|
|
||||||
rproc_remove_subdev(rproc, &smd->subdev);
|
rproc_remove_subdev(rproc, &smd->subdev);
|
||||||
of_node_put(smd->node);
|
of_node_put(smd->node);
|
||||||
}
|
}
|
||||||
|
@ -157,7 +207,7 @@ static int ssr_notify_start(struct rproc_subdev *subdev)
|
||||||
return 0;
|
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);
|
struct qcom_rproc_ssr *ssr = to_ssr_subdev(subdev);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
#include <linux/remoteproc.h>
|
#include <linux/remoteproc.h>
|
||||||
#include "remoteproc_internal.h"
|
#include "remoteproc_internal.h"
|
||||||
|
#include <linux/soc/qcom/qmi.h>
|
||||||
|
|
||||||
|
struct qcom_sysmon;
|
||||||
|
|
||||||
struct qcom_rproc_glink {
|
struct qcom_rproc_glink {
|
||||||
struct rproc_subdev subdev;
|
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_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink);
|
||||||
void qcom_remove_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_add_smd_subdev(struct rproc *rproc, struct qcom_rproc_subdev *smd);
|
||||||
void qcom_remove_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);
|
const char *ssr_name);
|
||||||
void qcom_remove_ssr_subdev(struct rproc *rproc, struct qcom_rproc_ssr *ssr);
|
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
|
#endif
|
||||||
|
|
|
@ -168,6 +168,7 @@ struct q6v5 {
|
||||||
|
|
||||||
struct qcom_rproc_subdev smd_subdev;
|
struct qcom_rproc_subdev smd_subdev;
|
||||||
struct qcom_rproc_ssr ssr_subdev;
|
struct qcom_rproc_ssr ssr_subdev;
|
||||||
|
struct qcom_sysmon *sysmon;
|
||||||
bool need_mem_protection;
|
bool need_mem_protection;
|
||||||
int mpss_perm;
|
int mpss_perm;
|
||||||
int mba_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);
|
rproc_report_crash(qproc->rproc, RPROC_WATCHDOG);
|
||||||
|
|
||||||
if (!IS_ERR(msg))
|
|
||||||
msg[0] = '\0';
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
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);
|
rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR);
|
||||||
|
|
||||||
if (!IS_ERR(msg))
|
|
||||||
msg[0] = '\0';
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1215,6 +1210,7 @@ static int q6v5_probe(struct platform_device *pdev)
|
||||||
qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS);
|
qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS);
|
||||||
qcom_add_smd_subdev(rproc, &qproc->smd_subdev);
|
qcom_add_smd_subdev(rproc, &qproc->smd_subdev);
|
||||||
qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss");
|
qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss");
|
||||||
|
qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12);
|
||||||
|
|
||||||
ret = rproc_add(rproc);
|
ret = rproc_add(rproc);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -1234,6 +1230,7 @@ static int q6v5_remove(struct platform_device *pdev)
|
||||||
|
|
||||||
rproc_del(qproc->rproc);
|
rproc_del(qproc->rproc);
|
||||||
|
|
||||||
|
qcom_remove_sysmon_subdev(qproc->sysmon);
|
||||||
qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev);
|
qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev);
|
||||||
qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev);
|
qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev);
|
||||||
rproc_free(qproc->rproc);
|
rproc_free(qproc->rproc);
|
||||||
|
|
|
@ -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");
|
|
@ -40,6 +40,7 @@
|
||||||
#define WCNSS_CRASH_REASON_SMEM 422
|
#define WCNSS_CRASH_REASON_SMEM 422
|
||||||
#define WCNSS_FIRMWARE_NAME "wcnss.mdt"
|
#define WCNSS_FIRMWARE_NAME "wcnss.mdt"
|
||||||
#define WCNSS_PAS_ID 6
|
#define WCNSS_PAS_ID 6
|
||||||
|
#define WCNSS_SSCTL_ID 0x13
|
||||||
|
|
||||||
#define WCNSS_SPARE_NVBIN_DLND BIT(25)
|
#define WCNSS_SPARE_NVBIN_DLND BIT(25)
|
||||||
|
|
||||||
|
@ -98,6 +99,7 @@ struct qcom_wcnss {
|
||||||
size_t mem_size;
|
size_t mem_size;
|
||||||
|
|
||||||
struct qcom_rproc_subdev smd_subdev;
|
struct qcom_rproc_subdev smd_subdev;
|
||||||
|
struct qcom_sysmon *sysmon;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct wcnss_data riva_data = {
|
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;
|
struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
|
||||||
|
|
||||||
return qcom_mdt_load(wcnss->dev, fw, rproc->firmware, WCNSS_PAS_ID,
|
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)
|
static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss)
|
||||||
|
@ -308,6 +311,7 @@ static const struct rproc_ops wcnss_ops = {
|
||||||
.start = wcnss_start,
|
.start = wcnss_start,
|
||||||
.stop = wcnss_stop,
|
.stop = wcnss_stop,
|
||||||
.da_to_va = wcnss_da_to_va,
|
.da_to_va = wcnss_da_to_va,
|
||||||
|
.parse_fw = qcom_register_dump_segments,
|
||||||
.load = wcnss_load,
|
.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);
|
rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR);
|
||||||
|
|
||||||
if (!IS_ERR(msg))
|
|
||||||
msg[0] = '\0';
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +552,7 @@ static int wcnss_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
|
|
||||||
qcom_add_smd_subdev(rproc, &wcnss->smd_subdev);
|
qcom_add_smd_subdev(rproc, &wcnss->smd_subdev);
|
||||||
|
wcnss->sysmon = qcom_add_sysmon_subdev(rproc, "wcnss", WCNSS_SSCTL_ID);
|
||||||
|
|
||||||
ret = rproc_add(rproc);
|
ret = rproc_add(rproc);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -573,6 +575,7 @@ static int wcnss_remove(struct platform_device *pdev)
|
||||||
qcom_smem_state_put(wcnss->state);
|
qcom_smem_state_put(wcnss->state);
|
||||||
rproc_del(wcnss->rproc);
|
rproc_del(wcnss->rproc);
|
||||||
|
|
||||||
|
qcom_remove_sysmon_subdev(wcnss->sysmon);
|
||||||
qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
|
qcom_remove_smd_subdev(wcnss->rproc, &wcnss->smd_subdev);
|
||||||
rproc_free(wcnss->rproc);
|
rproc_free(wcnss->rproc);
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/devcoredump.h>
|
||||||
#include <linux/remoteproc.h>
|
#include <linux/remoteproc.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
#include <linux/idr.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);
|
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);
|
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:
|
unroll_registration:
|
||||||
list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node)
|
list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node)
|
||||||
subdev->remove(subdev);
|
subdev->remove(subdev, true);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rproc_remove_subdevices(struct rproc *rproc)
|
static void rproc_remove_subdevices(struct rproc *rproc, bool crashed)
|
||||||
{
|
{
|
||||||
struct rproc_subdev *subdev;
|
struct rproc_subdev *subdev;
|
||||||
|
|
||||||
list_for_each_entry_reverse(subdev, &rproc->subdevs, node)
|
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 */
|
/* clean up remote vdev entries */
|
||||||
list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
|
list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
|
||||||
kref_put(&rvdev->refcount, rproc_vdev_release);
|
kref_put(&rvdev->refcount, rproc_vdev_release);
|
||||||
|
|
||||||
|
rproc_coredump_cleanup(rproc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rproc_start(struct rproc *rproc, const struct firmware *fw)
|
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);
|
rproc->bootaddr = rproc_get_boot_addr(rproc, fw);
|
||||||
|
|
||||||
/* load resource table */
|
/* Load resource table, core dump segment list etc from the firmware */
|
||||||
ret = rproc_load_rsc_table(rproc, fw);
|
ret = rproc_parse_fw(rproc, fw);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto disable_iommu;
|
goto disable_iommu;
|
||||||
|
|
||||||
|
@ -992,13 +1009,13 @@ static int rproc_trigger_auto_boot(struct rproc *rproc)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rproc_stop(struct rproc *rproc)
|
static int rproc_stop(struct rproc *rproc, bool crashed)
|
||||||
{
|
{
|
||||||
struct device *dev = &rproc->dev;
|
struct device *dev = &rproc->dev;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* remove any subdevices for the remote processor */
|
/* remove any subdevices for the remote processor */
|
||||||
rproc_remove_subdevices(rproc);
|
rproc_remove_subdevices(rproc, crashed);
|
||||||
|
|
||||||
/* the installed resource table is no longer accessible */
|
/* the installed resource table is no longer accessible */
|
||||||
rproc->table_ptr = rproc->cached_table;
|
rproc->table_ptr = rproc->cached_table;
|
||||||
|
@ -1017,6 +1034,113 @@ static int rproc_stop(struct rproc *rproc)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_trigger_recovery() - recover a remoteproc
|
||||||
* @rproc: the remote processor
|
* @rproc: the remote processor
|
||||||
|
@ -1039,10 +1163,13 @@ int rproc_trigger_recovery(struct rproc *rproc)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ret = rproc_stop(rproc);
|
ret = rproc_stop(rproc, false);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto unlock_mutex;
|
goto unlock_mutex;
|
||||||
|
|
||||||
|
/* generate coredump */
|
||||||
|
rproc_coredump(rproc);
|
||||||
|
|
||||||
/* load firmware */
|
/* load firmware */
|
||||||
ret = request_firmware(&firmware_p, rproc->firmware, dev);
|
ret = request_firmware(&firmware_p, rproc->firmware, dev);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1189,7 +1316,7 @@ void rproc_shutdown(struct rproc *rproc)
|
||||||
if (!atomic_dec_and_test(&rproc->power))
|
if (!atomic_dec_and_test(&rproc->power))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
ret = rproc_stop(rproc);
|
ret = rproc_stop(rproc, true);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
atomic_inc(&rproc->power);
|
atomic_inc(&rproc->power);
|
||||||
goto out;
|
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 */
|
/* Default to ELF loader if no load function is specified */
|
||||||
if (!rproc->ops->load) {
|
if (!rproc->ops->load) {
|
||||||
rproc->ops->load = rproc_elf_load_segments;
|
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->find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table;
|
||||||
rproc->ops->sanity_check = rproc_elf_sanity_check;
|
rproc->ops->sanity_check = rproc_elf_sanity_check;
|
||||||
rproc->ops->get_boot_addr = rproc_elf_get_boot_addr;
|
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->traces);
|
||||||
INIT_LIST_HEAD(&rproc->rvdevs);
|
INIT_LIST_HEAD(&rproc->rvdevs);
|
||||||
INIT_LIST_HEAD(&rproc->subdevs);
|
INIT_LIST_HEAD(&rproc->subdevs);
|
||||||
|
INIT_LIST_HEAD(&rproc->dump_segments);
|
||||||
|
|
||||||
INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work);
|
INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work);
|
||||||
|
|
||||||
|
@ -1535,7 +1663,7 @@ EXPORT_SYMBOL(rproc_del);
|
||||||
void rproc_add_subdev(struct rproc *rproc,
|
void rproc_add_subdev(struct rproc *rproc,
|
||||||
struct rproc_subdev *subdev,
|
struct rproc_subdev *subdev,
|
||||||
int (*probe)(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->probe = probe;
|
||||||
subdev->remove = remove;
|
subdev->remove = remove;
|
||||||
|
|
|
@ -88,11 +88,10 @@ int rproc_load_segments(struct rproc *rproc, const struct firmware *fw)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int rproc_load_rsc_table(struct rproc *rproc,
|
static inline int rproc_parse_fw(struct rproc *rproc, const struct firmware *fw)
|
||||||
const struct firmware *fw)
|
|
||||||
{
|
{
|
||||||
if (rproc->ops->load_rsc_table)
|
if (rproc->ops->parse_fw)
|
||||||
return rproc->ops->load_rsc_table(rproc, fw);
|
return rproc->ops->parse_fw(rproc, fw);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ config QCOM_PM
|
||||||
|
|
||||||
config QCOM_QMI_HELPERS
|
config QCOM_QMI_HELPERS
|
||||||
tristate
|
tristate
|
||||||
depends on ARCH_QCOM
|
depends on ARCH_QCOM && NET
|
||||||
help
|
help
|
||||||
Helper library for handling QMI encoded messages. QMI encoded
|
Helper library for handling QMI encoded messages. QMI encoded
|
||||||
messages are used in communication between the majority of QRTR
|
messages are used in communication between the majority of QRTR
|
||||||
|
|
|
@ -83,12 +83,14 @@ EXPORT_SYMBOL_GPL(qcom_mdt_get_size);
|
||||||
* @mem_region: allocated memory region to load firmware into
|
* @mem_region: allocated memory region to load firmware into
|
||||||
* @mem_phys: physical address of allocated memory region
|
* @mem_phys: physical address of allocated memory region
|
||||||
* @mem_size: size of the 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.
|
* Returns 0 on success, negative errno otherwise.
|
||||||
*/
|
*/
|
||||||
int qcom_mdt_load(struct device *dev, const struct firmware *fw,
|
int qcom_mdt_load(struct device *dev, const struct firmware *fw,
|
||||||
const char *firmware, int pas_id, void *mem_region,
|
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 *phdrs;
|
||||||
const struct elf32_phdr *phdr;
|
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);
|
memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reloc_base)
|
||||||
|
*reloc_base = mem_reloc;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
kfree(fw_name);
|
kfree(fw_name);
|
||||||
|
|
||||||
|
|
|
@ -344,7 +344,7 @@ struct rproc_ops {
|
||||||
int (*stop)(struct rproc *rproc);
|
int (*stop)(struct rproc *rproc);
|
||||||
void (*kick)(struct rproc *rproc, int vqid);
|
void (*kick)(struct rproc *rproc, int vqid);
|
||||||
void * (*da_to_va)(struct rproc *rproc, u64 da, int len);
|
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 resource_table *(*find_loaded_rsc_table)(
|
||||||
struct rproc *rproc, const struct firmware *fw);
|
struct rproc *rproc, const struct firmware *fw);
|
||||||
int (*load)(struct rproc *rproc, const struct firmware *fw);
|
int (*load)(struct rproc *rproc, const struct firmware *fw);
|
||||||
|
@ -394,6 +394,21 @@ enum rproc_crash_type {
|
||||||
RPROC_FATAL_ERROR,
|
RPROC_FATAL_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* struct rproc - represents a physical remote processor device
|
||||||
* @node: list node of this rproc object
|
* @node: list node of this rproc object
|
||||||
|
@ -424,6 +439,7 @@ enum rproc_crash_type {
|
||||||
* @cached_table: copy of the resource table
|
* @cached_table: copy of the resource table
|
||||||
* @table_sz: size of @cached_table
|
* @table_sz: size of @cached_table
|
||||||
* @has_iommu: flag to indicate if remote processor is behind an MMU
|
* @has_iommu: flag to indicate if remote processor is behind an MMU
|
||||||
|
* @dump_segments: list of segments in the firmware
|
||||||
*/
|
*/
|
||||||
struct rproc {
|
struct rproc {
|
||||||
struct list_head node;
|
struct list_head node;
|
||||||
|
@ -455,19 +471,21 @@ struct rproc {
|
||||||
size_t table_sz;
|
size_t table_sz;
|
||||||
bool has_iommu;
|
bool has_iommu;
|
||||||
bool auto_boot;
|
bool auto_boot;
|
||||||
|
struct list_head dump_segments;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct rproc_subdev - subdevice tied to a remoteproc
|
* struct rproc_subdev - subdevice tied to a remoteproc
|
||||||
* @node: list node related to the rproc subdevs list
|
* @node: list node related to the rproc subdevs list
|
||||||
* @probe: probe function, called as the rproc is started
|
* @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 rproc_subdev {
|
||||||
struct list_head node;
|
struct list_head node;
|
||||||
|
|
||||||
int (*probe)(struct rproc_subdev *subdev);
|
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 */
|
/* we currently support only two vrings per rvdev */
|
||||||
|
@ -534,6 +552,7 @@ void rproc_free(struct rproc *rproc);
|
||||||
int rproc_boot(struct rproc *rproc);
|
int rproc_boot(struct rproc *rproc);
|
||||||
void rproc_shutdown(struct rproc *rproc);
|
void rproc_shutdown(struct rproc *rproc);
|
||||||
void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type);
|
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)
|
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,
|
void rproc_add_subdev(struct rproc *rproc,
|
||||||
struct rproc_subdev *subdev,
|
struct rproc_subdev *subdev,
|
||||||
int (*probe)(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);
|
void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev);
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ struct firmware;
|
||||||
ssize_t qcom_mdt_get_size(const struct firmware *fw);
|
ssize_t qcom_mdt_get_size(const struct firmware *fw);
|
||||||
int qcom_mdt_load(struct device *dev, 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,
|
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
|
#endif
|
||||||
|
|
|
@ -62,6 +62,16 @@ config SAMPLE_KDB
|
||||||
Build an example of how to dynamically add the hello
|
Build an example of how to dynamically add the hello
|
||||||
command to the kdb shell.
|
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
|
config SAMPLE_RPMSG_CLIENT
|
||||||
tristate "Build rpmsg client sample -- loadable modules only"
|
tristate "Build rpmsg client sample -- loadable modules only"
|
||||||
depends on RPMSG && m
|
depends on RPMSG && m
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \
|
obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \
|
||||||
hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
|
hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
|
||||||
configfs/ connector/ v4l/ trace_printk/ \
|
configfs/ connector/ v4l/ trace_printk/ \
|
||||||
vfio-mdev/ statx/
|
vfio-mdev/ statx/ qmi/
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi_sample_client.o
|
|
@ -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");
|
Loading…
Reference in New Issue