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:
Linus Torvalds 2018-04-10 12:09:27 -07:00
commit 92589cbdda
21 changed files with 1524 additions and 56 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
} }

View File

@ -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[] = {

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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");

View File

@ -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);

View File

@ -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;

View File

@ -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;
} }

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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/

1
samples/qmi/Makefile Normal file
View File

@ -0,0 +1 @@
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi_sample_client.o

View File

@ -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");