Merge remote-tracking branch 'rpmsg/for-next'
authorStephen Rothwell <sfr@canb.auug.org.au>
Tue, 13 Sep 2016 03:07:12 +0000 (13:07 +1000)
committerStephen Rothwell <sfr@canb.auug.org.au>
Tue, 13 Sep 2016 03:07:12 +0000 (13:07 +1000)
25 files changed:
Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt [new file with mode: 0644]
drivers/remoteproc/Kconfig
drivers/remoteproc/Makefile
drivers/remoteproc/da8xx_remoteproc.c
drivers/remoteproc/omap_remoteproc.c
drivers/remoteproc/qcom_q6v5_pil.c
drivers/remoteproc/qcom_wcnss.c [new file with mode: 0644]
drivers/remoteproc/qcom_wcnss.h [new file with mode: 0644]
drivers/remoteproc/qcom_wcnss_iris.c [new file with mode: 0644]
drivers/remoteproc/remoteproc_core.c
drivers/remoteproc/remoteproc_debugfs.c
drivers/remoteproc/remoteproc_elf_loader.c
drivers/remoteproc/remoteproc_internal.h
drivers/remoteproc/remoteproc_virtio.c
drivers/remoteproc/wkup_m3_rproc.c
drivers/rpmsg/Kconfig
drivers/rpmsg/Makefile
drivers/rpmsg/qcom_smd.c [new file with mode: 0644]
drivers/rpmsg/rpmsg_core.c [new file with mode: 0644]
drivers/rpmsg/rpmsg_internal.h [new file with mode: 0644]
drivers/rpmsg/virtio_rpmsg_bus.c
include/linux/platform_data/remoteproc-omap.h
include/linux/remoteproc.h
include/linux/rpmsg.h
samples/rpmsg/rpmsg_client_sample.c

diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt b/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt
new file mode 100644 (file)
index 0000000..0d2361e
--- /dev/null
@@ -0,0 +1,132 @@
+Qualcomm WCNSS Peripheral Image Loader
+
+This document defines the binding for a component that loads and boots firmware
+on the Qualcomm WCNSS core.
+
+- compatible:
+       Usage: required
+       Value type: <string>
+       Definition: must be one of:
+                   "qcom,riva-pil",
+                   "qcom,pronto-v1-pil",
+                   "qcom,pronto-v2-pil"
+
+- reg:
+       Usage: required
+       Value type: <prop-encoded-array>
+       Definition: must specify the base address and size of the CCU, DXE and
+                   PMU register blocks
+
+- reg-names:
+       Usage: required
+       Value type: <stringlist>
+       Definition: must be "ccu", "dxe", "pmu"
+
+- interrupts-extended:
+       Usage: required
+       Value type: <prop-encoded-array>
+       Definition: must list the watchdog and fatal IRQs and may specify the
+                   ready, handover and stop-ack IRQs
+
+- interrupt-names:
+       Usage: required
+       Value type: <stringlist>
+       Definition: should be "wdog", "fatal", optionally followed by "ready",
+                   "handover", "stop-ack"
+
+- vddmx-supply:
+- vddcx-supply:
+- vddpx-supply:
+       Usage: required
+       Value type: <phandle>
+       Definition: reference to the regulators to be held on behalf of the
+                   booting of the WCNSS core
+
+- qcom,smem-states:
+       Usage: optional
+       Value type: <prop-encoded-array>
+       Definition: reference to the SMEM state used to indicate to WCNSS that
+                   it should shut down
+
+- qcom,smem-state-names:
+       Usage: optional
+       Value type: <stringlist>
+       Definition: should be "stop"
+
+- memory-region:
+       Usage: required
+       Value type: <prop-encoded-array>
+       Definition: reference to reserved-memory node for the remote processor
+                   see ../reserved-memory/reserved-memory.txt
+
+= SUBNODES
+A single subnode of the WCNSS PIL describes the attached rf module and its
+resource dependencies.
+
+- compatible:
+       Usage: required
+       Value type: <string>
+       Definition: must be one of:
+                   "qcom,wcn3620",
+                   "qcom,wcn3660",
+                   "qcom,wcn3680"
+
+- clocks:
+       Usage: required
+       Value type: <prop-encoded-array>
+       Definition: should specify the xo clock and optionally the rf clock
+
+- clock-names:
+       Usage: required
+       Value type: <stringlist>
+       Definition: should be "xo", optionally followed by "rf"
+
+- vddxo-supply:
+- vddrfa-supply:
+- vddpa-supply:
+- vdddig-supply:
+       Usage: required
+       Value type: <phandle>
+       Definition: reference to the regulators to be held on behalf of the
+                   booting of the WCNSS core
+
+= EXAMPLE
+The following example describes the resources needed to boot control the WCNSS,
+with attached WCN3680, as it is commonly found on MSM8974 boards.
+
+pronto@fb204000 {
+       compatible = "qcom,pronto-v2-pil";
+       reg = <0xfb204000 0x2000>, <0xfb202000 0x1000>, <0xfb21b000 0x3000>;
+       reg-names = "ccu", "dxe", "pmu";
+
+       interrupts-extended = <&intc 0 149 1>,
+                             <&wcnss_smp2p_slave 0 0>,
+                             <&wcnss_smp2p_slave 1 0>,
+                             <&wcnss_smp2p_slave 2 0>,
+                             <&wcnss_smp2p_slave 3 0>;
+       interrupt-names = "wdog", "fatal", "ready", "handover", "stop-ack";
+
+       vddmx-supply = <&pm8841_s1>;
+       vddcx-supply = <&pm8841_s2>;
+       vddpx-supply = <&pm8941_s3>;
+
+       qcom,smem-states = <&wcnss_smp2p_out 0>;
+       qcom,smem-state-names = "stop";
+
+       memory-region = <&wcnss_region>;
+
+       pinctrl-names = "default";
+       pinctrl-0 = <&wcnss_pin_a>;
+
+       iris {
+               compatible = "qcom,wcn3680";
+
+               clocks = <&rpmcc RPM_CXO_CLK_SRC>, <&rpmcc RPM_CXO_A2>;
+               clock-names = "xo", "rf";
+
+               vddxo-supply = <&pm8941_l6>;
+               vddrfa-supply = <&pm8941_l11>;
+               vddpa-supply = <&pm8941_l19>;
+               vdddig-supply = <&pm8941_s3>;
+       };
+};
index 1a8bf76a925fa86e12c00e9c2a1e097a5dda333a..f396bfef5d42863b4f3b9609c9798b4df4ce6059 100644 (file)
@@ -17,7 +17,7 @@ config OMAP_REMOTEPROC
        select REMOTEPROC
        select MAILBOX
        select OMAP2PLUS_MBOX
-       select RPMSG
+       select RPMSG_VIRTIO
        help
          Say y here to support OMAP's remote processors (dual M3
          and DSP on OMAP4) via the remote processor framework.
@@ -59,7 +59,7 @@ config DA8XX_REMOTEPROC
        depends on ARCH_DAVINCI_DA8XX
        select CMA if MMU
        select REMOTEPROC
-       select RPMSG
+       select RPMSG_VIRTIO
        help
          Say y here to support DA8xx/OMAP-L13x remote processors via the
          remote processor framework.
@@ -91,6 +91,22 @@ config QCOM_Q6V5_PIL
          Say y here to support the Qualcomm Peripherial Image Loader for the
          Hexagon V5 based remote processors.
 
+config QCOM_WCNSS_IRIS
+       tristate
+       depends on OF && ARCH_QCOM
+
+config QCOM_WCNSS_PIL
+       tristate "Qualcomm WCNSS Peripheral Image Loader"
+       depends on OF && ARCH_QCOM
+       depends on QCOM_SMEM
+       select QCOM_MDT_LOADER
+       select QCOM_SCM
+       select QCOM_WCNSS_IRIS
+       select REMOTEPROC
+       help
+         Say y here to support the Peripheral Image Loader for the Qualcomm
+         Wireless Connectivity Subsystem.
+
 config ST_REMOTEPROC
        tristate "ST remoteproc support"
        depends on ARCH_STI
index 92d3758bd15cc7c7c65b6f6c21d5c1d06c2d2a42..6dfb62ed643f8ea46db285b4fc77a60ca4f8f3ee 100644 (file)
@@ -13,4 +13,6 @@ obj-$(CONFIG_WKUP_M3_RPROC)           += wkup_m3_rproc.o
 obj-$(CONFIG_DA8XX_REMOTEPROC)         += da8xx_remoteproc.o
 obj-$(CONFIG_QCOM_MDT_LOADER)          += qcom_mdt_loader.o
 obj-$(CONFIG_QCOM_Q6V5_PIL)            += qcom_q6v5_pil.o
+obj-$(CONFIG_QCOM_WCNSS_IRIS)          += qcom_wcnss_iris.o
+obj-$(CONFIG_QCOM_WCNSS_PIL)           += qcom_wcnss.o
 obj-$(CONFIG_ST_REMOTEPROC)            += st_remoteproc.o
index 009e56f67de239779e3710bffc1b430257441ea3..12823d078e1ebb375e938fbe2a7c4cc81a57d19c 100644 (file)
@@ -147,7 +147,7 @@ static void da8xx_rproc_kick(struct rproc *rproc, int vqid)
 {
        struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
 
-       /* Interupt remote proc */
+       /* Interrupt remote proc */
        writel(SYSCFG_CHIPSIG2, drproc->chipsig);
 }
 
index b74368a9123508625122acfb56038dd26d4aebed..01e234cb9157db726553d2e2bcf484415d5d3532 100644 (file)
@@ -96,7 +96,8 @@ static void omap_rproc_kick(struct rproc *rproc, int vqid)
        /* send the index of the triggered virtqueue in the mailbox payload */
        ret = mbox_send_message(oproc->mbox, (void *)vqid);
        if (ret < 0)
-               dev_err(dev, "omap_mbox_msg_send failed: %d\n", ret);
+               dev_err(dev, "failed to send mailbox message, status = %d\n",
+                       ret);
 }
 
 /*
@@ -196,7 +197,7 @@ static int omap_rproc_probe(struct platform_device *pdev)
        }
 
        rproc = rproc_alloc(&pdev->dev, pdata->name, &omap_rproc_ops,
-                               pdata->firmware, sizeof(*oproc));
+                           pdata->firmware, sizeof(*oproc));
        if (!rproc)
                return -ENOMEM;
 
index 2a1b2c7d8f2cd175fd92f2aae92796209df2b1ea..05b04573e87d05564bf9dfca9863be69c83b490d 100644 (file)
@@ -863,8 +863,10 @@ static int q6v5_probe(struct platform_device *pdev)
                goto free_rproc;
 
        qproc->state = qcom_smem_state_get(&pdev->dev, "stop", &qproc->stop_bit);
-       if (IS_ERR(qproc->state))
+       if (IS_ERR(qproc->state)) {
+               ret = PTR_ERR(qproc->state);
                goto free_rproc;
+       }
 
        ret = rproc_add(rproc);
        if (ret)
diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c
new file mode 100644 (file)
index 0000000..1917de7
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+ * Qualcomm Wireless Connectivity Subsystem Peripheral Image Loader
+ *
+ * Copyright (C) 2016 Linaro Ltd
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/qcom_scm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/remoteproc.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/smem_state.h>
+
+#include "qcom_mdt_loader.h"
+#include "remoteproc_internal.h"
+#include "qcom_wcnss.h"
+
+#define WCNSS_CRASH_REASON_SMEM                422
+#define WCNSS_FIRMWARE_NAME            "wcnss.mdt"
+#define WCNSS_PAS_ID                   6
+
+#define WCNSS_SPARE_NVBIN_DLND         BIT(25)
+
+#define WCNSS_PMU_IRIS_XO_CFG          BIT(3)
+#define WCNSS_PMU_IRIS_XO_EN           BIT(4)
+#define WCNSS_PMU_GC_BUS_MUX_SEL_TOP   BIT(5)
+#define WCNSS_PMU_IRIS_XO_CFG_STS      BIT(6) /* 1: in progress, 0: done */
+
+#define WCNSS_PMU_IRIS_RESET           BIT(7)
+#define WCNSS_PMU_IRIS_RESET_STS       BIT(8) /* 1: in progress, 0: done */
+#define WCNSS_PMU_IRIS_XO_READ         BIT(9)
+#define WCNSS_PMU_IRIS_XO_READ_STS     BIT(10)
+
+#define WCNSS_PMU_XO_MODE_MASK         GENMASK(2, 1)
+#define WCNSS_PMU_XO_MODE_19p2         0
+#define WCNSS_PMU_XO_MODE_48           3
+
+struct wcnss_data {
+       size_t pmu_offset;
+       size_t spare_offset;
+
+       const struct wcnss_vreg_info *vregs;
+       size_t num_vregs;
+};
+
+struct qcom_wcnss {
+       struct device *dev;
+       struct rproc *rproc;
+
+       void __iomem *pmu_cfg;
+       void __iomem *spare_out;
+
+       bool use_48mhz_xo;
+
+       int wdog_irq;
+       int fatal_irq;
+       int ready_irq;
+       int handover_irq;
+       int stop_ack_irq;
+
+       struct qcom_smem_state *state;
+       unsigned stop_bit;
+
+       struct mutex iris_lock;
+       struct qcom_iris *iris;
+
+       struct regulator_bulk_data *vregs;
+       size_t num_vregs;
+
+       struct completion start_done;
+       struct completion stop_done;
+
+       phys_addr_t mem_phys;
+       phys_addr_t mem_reloc;
+       void *mem_region;
+       size_t mem_size;
+};
+
+static const struct wcnss_data riva_data = {
+       .pmu_offset = 0x28,
+       .spare_offset = 0xb4,
+
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddmx",  1050000, 1150000, 0 },
+               { "vddcx",  1050000, 1150000, 0 },
+               { "vddpx",  1800000, 1800000, 0 },
+       },
+       .num_vregs = 3,
+};
+
+static const struct wcnss_data pronto_v1_data = {
+       .pmu_offset = 0x1004,
+       .spare_offset = 0x1088,
+
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddmx", 950000, 1150000, 0 },
+               { "vddcx", .super_turbo = true},
+               { "vddpx", 1800000, 1800000, 0 },
+       },
+       .num_vregs = 3,
+};
+
+static const struct wcnss_data pronto_v2_data = {
+       .pmu_offset = 0x1004,
+       .spare_offset = 0x1088,
+
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddmx", 1287500, 1287500, 0 },
+               { "vddcx", .super_turbo = true },
+               { "vddpx", 1800000, 1800000, 0 },
+       },
+       .num_vregs = 3,
+};
+
+void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss,
+                           struct qcom_iris *iris,
+                           bool use_48mhz_xo)
+{
+       mutex_lock(&wcnss->iris_lock);
+
+       wcnss->iris = iris;
+       wcnss->use_48mhz_xo = use_48mhz_xo;
+
+       mutex_unlock(&wcnss->iris_lock);
+}
+EXPORT_SYMBOL_GPL(qcom_wcnss_assign_iris);
+
+static int wcnss_load(struct rproc *rproc, const struct firmware *fw)
+{
+       struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
+       phys_addr_t fw_addr;
+       size_t fw_size;
+       bool relocate;
+       int ret;
+
+       ret = qcom_scm_pas_init_image(WCNSS_PAS_ID, fw->data, fw->size);
+       if (ret) {
+               dev_err(&rproc->dev, "invalid firmware metadata\n");
+               return ret;
+       }
+
+       ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate);
+       if (ret) {
+               dev_err(&rproc->dev, "failed to parse mdt header\n");
+               return ret;
+       }
+
+       if (relocate) {
+               wcnss->mem_reloc = fw_addr;
+
+               ret = qcom_scm_pas_mem_setup(WCNSS_PAS_ID, wcnss->mem_phys, fw_size);
+               if (ret) {
+                       dev_err(&rproc->dev, "unable to setup memory for image\n");
+                       return ret;
+               }
+       }
+
+       return qcom_mdt_load(rproc, fw, rproc->firmware);
+}
+
+static const struct rproc_fw_ops wcnss_fw_ops = {
+       .find_rsc_table = qcom_mdt_find_rsc_table,
+       .load = wcnss_load,
+};
+
+static void wcnss_indicate_nv_download(struct qcom_wcnss *wcnss)
+{
+       u32 val;
+
+       /* Indicate NV download capability */
+       val = readl(wcnss->spare_out);
+       val |= WCNSS_SPARE_NVBIN_DLND;
+       writel(val, wcnss->spare_out);
+}
+
+static void wcnss_configure_iris(struct qcom_wcnss *wcnss)
+{
+       u32 val;
+
+       /* Clear PMU cfg register */
+       writel(0, wcnss->pmu_cfg);
+
+       val = WCNSS_PMU_GC_BUS_MUX_SEL_TOP | WCNSS_PMU_IRIS_XO_EN;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Clear XO_MODE */
+       val &= ~WCNSS_PMU_XO_MODE_MASK;
+       if (wcnss->use_48mhz_xo)
+               val |= WCNSS_PMU_XO_MODE_48 << 1;
+       else
+               val |= WCNSS_PMU_XO_MODE_19p2 << 1;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Reset IRIS */
+       val |= WCNSS_PMU_IRIS_RESET;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Wait for PMU.iris_reg_reset_sts */
+       while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_RESET_STS)
+               cpu_relax();
+
+       /* Clear IRIS reset */
+       val &= ~WCNSS_PMU_IRIS_RESET;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Start IRIS XO configuration */
+       val |= WCNSS_PMU_IRIS_XO_CFG;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Wait for XO configuration to finish */
+       while (readl(wcnss->pmu_cfg) & WCNSS_PMU_IRIS_XO_CFG_STS)
+               cpu_relax();
+
+       /* Stop IRIS XO configuration */
+       val &= ~WCNSS_PMU_GC_BUS_MUX_SEL_TOP;
+       val &= ~WCNSS_PMU_IRIS_XO_CFG;
+       writel(val, wcnss->pmu_cfg);
+
+       /* Add some delay for XO to settle */
+       msleep(20);
+}
+
+static int wcnss_start(struct rproc *rproc)
+{
+       struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
+       int ret;
+
+       mutex_lock(&wcnss->iris_lock);
+       if (!wcnss->iris) {
+               dev_err(wcnss->dev, "no iris registered\n");
+               ret = -EINVAL;
+               goto release_iris_lock;
+       }
+
+       ret = regulator_bulk_enable(wcnss->num_vregs, wcnss->vregs);
+       if (ret)
+               goto release_iris_lock;
+
+       ret = qcom_iris_enable(wcnss->iris);
+       if (ret)
+               goto disable_regulators;
+
+       wcnss_indicate_nv_download(wcnss);
+       wcnss_configure_iris(wcnss);
+
+       ret = qcom_scm_pas_auth_and_reset(WCNSS_PAS_ID);
+       if (ret) {
+               dev_err(wcnss->dev,
+                       "failed to authenticate image and release reset\n");
+               goto disable_iris;
+       }
+
+       ret = wait_for_completion_timeout(&wcnss->start_done,
+                                         msecs_to_jiffies(5000));
+       if (wcnss->ready_irq > 0 && ret == 0) {
+               /* We have a ready_irq, but it didn't fire in time. */
+               dev_err(wcnss->dev, "start timed out\n");
+               qcom_scm_pas_shutdown(WCNSS_PAS_ID);
+               ret = -ETIMEDOUT;
+               goto disable_iris;
+       }
+
+       ret = 0;
+
+disable_iris:
+       qcom_iris_disable(wcnss->iris);
+disable_regulators:
+       regulator_bulk_disable(wcnss->num_vregs, wcnss->vregs);
+release_iris_lock:
+       mutex_unlock(&wcnss->iris_lock);
+
+       return ret;
+}
+
+static int wcnss_stop(struct rproc *rproc)
+{
+       struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
+       int ret;
+
+       if (wcnss->state) {
+               qcom_smem_state_update_bits(wcnss->state,
+                                           BIT(wcnss->stop_bit),
+                                           BIT(wcnss->stop_bit));
+
+               ret = wait_for_completion_timeout(&wcnss->stop_done,
+                                                 msecs_to_jiffies(5000));
+               if (ret == 0)
+                       dev_err(wcnss->dev, "timed out on wait\n");
+
+               qcom_smem_state_update_bits(wcnss->state,
+                                           BIT(wcnss->stop_bit),
+                                           0);
+       }
+
+       ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID);
+       if (ret)
+               dev_err(wcnss->dev, "failed to shutdown: %d\n", ret);
+
+       return ret;
+}
+
+static void *wcnss_da_to_va(struct rproc *rproc, u64 da, int len)
+{
+       struct qcom_wcnss *wcnss = (struct qcom_wcnss *)rproc->priv;
+       int offset;
+
+       offset = da - wcnss->mem_reloc;
+       if (offset < 0 || offset + len > wcnss->mem_size)
+               return NULL;
+
+       return wcnss->mem_region + offset;
+}
+
+static const struct rproc_ops wcnss_ops = {
+       .start = wcnss_start,
+       .stop = wcnss_stop,
+       .da_to_va = wcnss_da_to_va,
+};
+
+static irqreturn_t wcnss_wdog_interrupt(int irq, void *dev)
+{
+       struct qcom_wcnss *wcnss = dev;
+
+       rproc_report_crash(wcnss->rproc, RPROC_WATCHDOG);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t wcnss_fatal_interrupt(int irq, void *dev)
+{
+       struct qcom_wcnss *wcnss = dev;
+       size_t len;
+       char *msg;
+
+       msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, WCNSS_CRASH_REASON_SMEM, &len);
+       if (!IS_ERR(msg) && len > 0 && msg[0])
+               dev_err(wcnss->dev, "fatal error received: %s\n", msg);
+
+       rproc_report_crash(wcnss->rproc, RPROC_FATAL_ERROR);
+
+       if (!IS_ERR(msg))
+               msg[0] = '\0';
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t wcnss_ready_interrupt(int irq, void *dev)
+{
+       struct qcom_wcnss *wcnss = dev;
+
+       complete(&wcnss->start_done);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t wcnss_handover_interrupt(int irq, void *dev)
+{
+       /*
+        * XXX: At this point we're supposed to release the resources that we
+        * have been holding on behalf of the WCNSS. Unfortunately this
+        * interrupt comes way before the other side seems to be done.
+        *
+        * So we're currently relying on the ready interrupt firing later then
+        * this and we just disable the resources at the end of wcnss_start().
+        */
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev)
+{
+       struct qcom_wcnss *wcnss = dev;
+
+       complete(&wcnss->stop_done);
+
+       return IRQ_HANDLED;
+}
+
+static int wcnss_init_regulators(struct qcom_wcnss *wcnss,
+                                const struct wcnss_vreg_info *info,
+                                int num_vregs)
+{
+       struct regulator_bulk_data *bulk;
+       int ret;
+       int i;
+
+       bulk = devm_kcalloc(wcnss->dev,
+                           num_vregs, sizeof(struct regulator_bulk_data),
+                           GFP_KERNEL);
+       if (!bulk)
+               return -ENOMEM;
+
+       for (i = 0; i < num_vregs; i++)
+               bulk[i].supply = info[i].name;
+
+       ret = devm_regulator_bulk_get(wcnss->dev, num_vregs, bulk);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < num_vregs; i++) {
+               if (info[i].max_voltage)
+                       regulator_set_voltage(bulk[i].consumer,
+                                             info[i].min_voltage,
+                                             info[i].max_voltage);
+
+               if (info[i].load_uA)
+                       regulator_set_load(bulk[i].consumer, info[i].load_uA);
+       }
+
+       wcnss->vregs = bulk;
+       wcnss->num_vregs = num_vregs;
+
+       return 0;
+}
+
+static int wcnss_request_irq(struct qcom_wcnss *wcnss,
+                            struct platform_device *pdev,
+                            const char *name,
+                            bool optional,
+                            irq_handler_t thread_fn)
+{
+       int ret;
+
+       ret = platform_get_irq_byname(pdev, name);
+       if (ret < 0 && optional) {
+               dev_dbg(&pdev->dev, "no %s IRQ defined, ignoring\n", name);
+               return 0;
+       } else if (ret < 0) {
+               dev_err(&pdev->dev, "no %s IRQ defined\n", name);
+               return ret;
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, ret,
+                                       NULL, thread_fn,
+                                       IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+                                       "wcnss", wcnss);
+       if (ret)
+               dev_err(&pdev->dev, "request %s IRQ failed\n", name);
+
+       return ret;
+}
+
+static int wcnss_alloc_memory_region(struct qcom_wcnss *wcnss)
+{
+       struct device_node *node;
+       struct resource r;
+       int ret;
+
+       node = of_parse_phandle(wcnss->dev->of_node, "memory-region", 0);
+       if (!node) {
+               dev_err(wcnss->dev, "no memory-region specified\n");
+               return -EINVAL;
+       }
+
+       ret = of_address_to_resource(node, 0, &r);
+       if (ret)
+               return ret;
+
+       wcnss->mem_phys = wcnss->mem_reloc = r.start;
+       wcnss->mem_size = resource_size(&r);
+       wcnss->mem_region = devm_ioremap_wc(wcnss->dev, wcnss->mem_phys, wcnss->mem_size);
+       if (!wcnss->mem_region) {
+               dev_err(wcnss->dev, "unable to map memory region: %pa+%zx\n",
+                       &r.start, wcnss->mem_size);
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
+static int wcnss_probe(struct platform_device *pdev)
+{
+       const struct wcnss_data *data;
+       struct qcom_wcnss *wcnss;
+       struct resource *res;
+       struct rproc *rproc;
+       void __iomem *mmio;
+       int ret;
+
+       data = of_device_get_match_data(&pdev->dev);
+
+       if (!qcom_scm_is_available())
+               return -EPROBE_DEFER;
+
+       if (!qcom_scm_pas_supported(WCNSS_PAS_ID)) {
+               dev_err(&pdev->dev, "PAS is not available for WCNSS\n");
+               return -ENXIO;
+       }
+
+       rproc = rproc_alloc(&pdev->dev, pdev->name, &wcnss_ops,
+                           WCNSS_FIRMWARE_NAME, sizeof(*wcnss));
+       if (!rproc) {
+               dev_err(&pdev->dev, "unable to allocate remoteproc\n");
+               return -ENOMEM;
+       }
+
+       rproc->fw_ops = &wcnss_fw_ops;
+
+       wcnss = (struct qcom_wcnss *)rproc->priv;
+       wcnss->dev = &pdev->dev;
+       wcnss->rproc = rproc;
+       platform_set_drvdata(pdev, wcnss);
+
+       init_completion(&wcnss->start_done);
+       init_completion(&wcnss->stop_done);
+
+       mutex_init(&wcnss->iris_lock);
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pmu");
+       mmio = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(mmio)) {
+               ret = PTR_ERR(mmio);
+               goto free_rproc;
+       };
+
+       ret = wcnss_alloc_memory_region(wcnss);
+       if (ret)
+               goto free_rproc;
+
+       wcnss->pmu_cfg = mmio + data->pmu_offset;
+       wcnss->spare_out = mmio + data->spare_offset;
+
+       ret = wcnss_init_regulators(wcnss, data->vregs, data->num_vregs);
+       if (ret)
+               goto free_rproc;
+
+       ret = wcnss_request_irq(wcnss, pdev, "wdog", false, wcnss_wdog_interrupt);
+       if (ret < 0)
+               goto free_rproc;
+       wcnss->wdog_irq = ret;
+
+       ret = wcnss_request_irq(wcnss, pdev, "fatal", false, wcnss_fatal_interrupt);
+       if (ret < 0)
+               goto free_rproc;
+       wcnss->fatal_irq = ret;
+
+       ret = wcnss_request_irq(wcnss, pdev, "ready", true, wcnss_ready_interrupt);
+       if (ret < 0)
+               goto free_rproc;
+       wcnss->ready_irq = ret;
+
+       ret = wcnss_request_irq(wcnss, pdev, "handover", true, wcnss_handover_interrupt);
+       if (ret < 0)
+               goto free_rproc;
+       wcnss->handover_irq = ret;
+
+       ret = wcnss_request_irq(wcnss, pdev, "stop-ack", true, wcnss_stop_ack_interrupt);
+       if (ret < 0)
+               goto free_rproc;
+       wcnss->stop_ack_irq = ret;
+
+       if (wcnss->stop_ack_irq) {
+               wcnss->state = qcom_smem_state_get(&pdev->dev, "stop",
+                                                  &wcnss->stop_bit);
+               if (IS_ERR(wcnss->state)) {
+                       ret = PTR_ERR(wcnss->state);
+                       goto free_rproc;
+               }
+       }
+
+       ret = rproc_add(rproc);
+       if (ret)
+               goto free_rproc;
+
+       return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+
+free_rproc:
+       rproc_put(rproc);
+
+       return ret;
+}
+
+static int wcnss_remove(struct platform_device *pdev)
+{
+       struct qcom_wcnss *wcnss = platform_get_drvdata(pdev);
+
+       of_platform_depopulate(&pdev->dev);
+
+       qcom_smem_state_put(wcnss->state);
+       rproc_del(wcnss->rproc);
+       rproc_put(wcnss->rproc);
+
+       return 0;
+}
+
+static const struct of_device_id wcnss_of_match[] = {
+       { .compatible = "qcom,riva-pil", &riva_data },
+       { .compatible = "qcom,pronto-v1-pil", &pronto_v1_data },
+       { .compatible = "qcom,pronto-v2-pil", &pronto_v2_data },
+       { },
+};
+
+static struct platform_driver wcnss_driver = {
+       .probe = wcnss_probe,
+       .remove = wcnss_remove,
+       .driver = {
+               .name = "qcom-wcnss-pil",
+               .of_match_table = wcnss_of_match,
+       },
+};
+
+module_platform_driver(wcnss_driver);
+MODULE_DESCRIPTION("Qualcomm Peripherial Image Loader for Wireless Subsystem");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/remoteproc/qcom_wcnss.h b/drivers/remoteproc/qcom_wcnss.h
new file mode 100644 (file)
index 0000000..9dc4a9f
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef __QCOM_WNCSS_H__
+#define __QCOM_WNCSS_H__
+
+struct qcom_iris;
+struct qcom_wcnss;
+
+struct wcnss_vreg_info {
+       const char * const name;
+       int min_voltage;
+       int max_voltage;
+
+       int load_uA;
+
+       bool super_turbo;
+};
+
+int qcom_iris_enable(struct qcom_iris *iris);
+void qcom_iris_disable(struct qcom_iris *iris);
+
+void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss, struct qcom_iris *iris, bool use_48mhz_xo);
+
+#endif
diff --git a/drivers/remoteproc/qcom_wcnss_iris.c b/drivers/remoteproc/qcom_wcnss_iris.c
new file mode 100644 (file)
index 0000000..f0ca24a
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Qualcomm Wireless Connectivity Subsystem Iris driver
+ *
+ * Copyright (C) 2016 Linaro Ltd
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include "qcom_wcnss.h"
+
+struct qcom_iris {
+       struct device *dev;
+
+       struct clk *xo_clk;
+
+       struct regulator_bulk_data *vregs;
+       size_t num_vregs;
+};
+
+struct iris_data {
+       const struct wcnss_vreg_info *vregs;
+       size_t num_vregs;
+
+       bool use_48mhz_xo;
+};
+
+static const struct iris_data wcn3620_data = {
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddxo",  1800000, 1800000, 10000 },
+               { "vddrfa", 1300000, 1300000, 100000 },
+               { "vddpa",  3300000, 3300000, 515000 },
+               { "vdddig", 1800000, 1800000, 10000 },
+       },
+       .num_vregs = 4,
+       .use_48mhz_xo = false,
+};
+
+static const struct iris_data wcn3660_data = {
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddxo",  1800000, 1800000, 10000 },
+               { "vddrfa", 1300000, 1300000, 100000 },
+               { "vddpa",  2900000, 3000000, 515000 },
+               { "vdddig", 1200000, 1225000, 10000 },
+       },
+       .num_vregs = 4,
+       .use_48mhz_xo = true,
+};
+
+static const struct iris_data wcn3680_data = {
+       .vregs = (struct wcnss_vreg_info[]) {
+               { "vddxo",  1800000, 1800000, 10000 },
+               { "vddrfa", 1300000, 1300000, 100000 },
+               { "vddpa",  3300000, 3300000, 515000 },
+               { "vdddig", 1800000, 1800000, 10000 },
+       },
+       .num_vregs = 4,
+       .use_48mhz_xo = true,
+};
+
+int qcom_iris_enable(struct qcom_iris *iris)
+{
+       int ret;
+
+       ret = regulator_bulk_enable(iris->num_vregs, iris->vregs);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(iris->xo_clk);
+       if (ret) {
+               dev_err(iris->dev, "failed to enable xo clk\n");
+               goto disable_regulators;
+       }
+
+       return 0;
+
+disable_regulators:
+       regulator_bulk_disable(iris->num_vregs, iris->vregs);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(qcom_iris_enable);
+
+void qcom_iris_disable(struct qcom_iris *iris)
+{
+       clk_disable_unprepare(iris->xo_clk);
+       regulator_bulk_disable(iris->num_vregs, iris->vregs);
+}
+EXPORT_SYMBOL_GPL(qcom_iris_disable);
+
+static int qcom_iris_probe(struct platform_device *pdev)
+{
+       const struct iris_data *data;
+       struct qcom_wcnss *wcnss;
+       struct qcom_iris *iris;
+       int ret;
+       int i;
+
+       iris = devm_kzalloc(&pdev->dev, sizeof(struct qcom_iris), GFP_KERNEL);
+       if (!iris)
+               return -ENOMEM;
+
+       data = of_device_get_match_data(&pdev->dev);
+       wcnss = dev_get_drvdata(pdev->dev.parent);
+
+       iris->xo_clk = devm_clk_get(&pdev->dev, "xo");
+       if (IS_ERR(iris->xo_clk)) {
+               if (PTR_ERR(iris->xo_clk) != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "failed to acquire xo clk\n");
+               return PTR_ERR(iris->xo_clk);
+       }
+
+       iris->num_vregs = data->num_vregs;
+       iris->vregs = devm_kcalloc(&pdev->dev,
+                                  iris->num_vregs,
+                                  sizeof(struct regulator_bulk_data),
+                                  GFP_KERNEL);
+       if (!iris->vregs)
+               return -ENOMEM;
+
+       for (i = 0; i < iris->num_vregs; i++)
+               iris->vregs[i].supply = data->vregs[i].name;
+
+       ret = devm_regulator_bulk_get(&pdev->dev, iris->num_vregs, iris->vregs);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get regulators\n");
+               return ret;
+       }
+
+       for (i = 0; i < iris->num_vregs; i++) {
+               if (data->vregs[i].max_voltage)
+                       regulator_set_voltage(iris->vregs[i].consumer,
+                                             data->vregs[i].min_voltage,
+                                             data->vregs[i].max_voltage);
+
+               if (data->vregs[i].load_uA)
+                       regulator_set_load(iris->vregs[i].consumer,
+                                          data->vregs[i].load_uA);
+       }
+
+       qcom_wcnss_assign_iris(wcnss, iris, data->use_48mhz_xo);
+
+       return 0;
+}
+
+static int qcom_iris_remove(struct platform_device *pdev)
+{
+       struct qcom_wcnss *wcnss = dev_get_drvdata(pdev->dev.parent);
+
+       qcom_wcnss_assign_iris(wcnss, NULL, false);
+
+       return 0;
+}
+
+static const struct of_device_id iris_of_match[] = {
+       { .compatible = "qcom,wcn3620", .data = &wcn3620_data },
+       { .compatible = "qcom,wcn3660", .data = &wcn3660_data },
+       { .compatible = "qcom,wcn3680", .data = &wcn3680_data },
+       {}
+};
+
+static struct platform_driver wcnss_driver = {
+       .probe = qcom_iris_probe,
+       .remove = qcom_iris_remove,
+       .driver = {
+               .name = "qcom-iris",
+               .of_match_table = iris_of_match,
+       },
+};
+
+module_platform_driver(wcnss_driver);
+MODULE_DESCRIPTION("Qualcomm Wireless Subsystem Iris driver");
+MODULE_LICENSE("GPL v2");
index fe0539ed9cb5911e105732ef68d226499fa9ffb9..0d3c191b6bc356a0fc0f525588b4f161b96579db 100644 (file)
@@ -78,7 +78,7 @@ static const char *rproc_crash_to_string(enum rproc_crash_type type)
  * will try to access an unmapped device address.
  */
 static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev,
-               unsigned long iova, int flags, void *token)
+                            unsigned long iova, int flags, void *token)
 {
        struct rproc *rproc = token;
 
@@ -236,8 +236,8 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
        }
        notifyid = ret;
 
-       dev_dbg(dev, "vring%d: va %p dma %llx size %x idr %d\n", i, va,
-                               (unsigned long long)dma, size, notifyid);
+       dev_dbg(dev, "vring%d: va %p dma %pad size 0x%x idr %d\n",
+               i, va, &dma, size, notifyid);
 
        rvring->va = va;
        rvring->dma = dma;
@@ -263,19 +263,13 @@ rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i)
        struct fw_rsc_vdev_vring *vring = &rsc->vring[i];
        struct rproc_vring *rvring = &rvdev->vring[i];
 
-       dev_dbg(dev, "vdev rsc: vring%d: da %x, qsz %d, align %d\n",
-                               i, vring->da, vring->num, vring->align);
-
-       /* make sure reserved bytes are zeroes */
-       if (vring->reserved) {
-               dev_err(dev, "vring rsc has non zero reserved bytes\n");
-               return -EINVAL;
-       }
+       dev_dbg(dev, "vdev rsc: vring%d: da 0x%x, qsz %d, align %d\n",
+               i, vring->da, vring->num, vring->align);
 
        /* verify queue size and vring alignment are sane */
        if (!vring->num || !vring->align) {
                dev_err(dev, "invalid qsz (%d) or alignment (%d)\n",
-                                               vring->num, vring->align);
+                       vring->num, vring->align);
                return -EINVAL;
        }
 
@@ -330,7 +324,7 @@ void rproc_free_vring(struct rproc_vring *rvring)
  * Returns 0 on success, or an appropriate error code otherwise
  */
 static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
-                                                       int offset, int avail)
+                            int offset, int avail)
 {
        struct device *dev = &rproc->dev;
        struct rproc_vdev *rvdev;
@@ -349,7 +343,7 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
                return -EINVAL;
        }
 
-       dev_dbg(dev, "vdev rsc: id %d, dfeatures %x, cfg len %d, %d vrings\n",
+       dev_dbg(dev, "vdev rsc: id %d, dfeatures 0x%x, cfg len %d, %d vrings\n",
                rsc->id, rsc->dfeatures, rsc->config_len, rsc->num_of_vrings);
 
        /* we currently support only two vrings per rvdev */
@@ -358,7 +352,7 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc,
                return -EINVAL;
        }
 
-       rvdev = kzalloc(sizeof(struct rproc_vdev), GFP_KERNEL);
+       rvdev = kzalloc(sizeof(*rvdev), GFP_KERNEL);
        if (!rvdev)
                return -ENOMEM;
 
@@ -407,7 +401,7 @@ free_rvdev:
  * Returns 0 on success, or an appropriate error code otherwise
  */
 static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
-                                                       int offset, int avail)
+                             int offset, int avail)
 {
        struct rproc_mem_entry *trace;
        struct device *dev = &rproc->dev;
@@ -455,8 +449,8 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
 
        rproc->num_traces++;
 
-       dev_dbg(dev, "%s added: va %p, da 0x%x, len 0x%x\n", name, ptr,
-                                               rsc->da, rsc->len);
+       dev_dbg(dev, "%s added: va %p, da 0x%x, len 0x%x\n",
+               name, ptr, rsc->da, rsc->len);
 
        return 0;
 }
@@ -487,7 +481,7 @@ static int rproc_handle_trace(struct rproc *rproc, struct fw_rsc_trace *rsc,
  * are outside those ranges.
  */
 static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc,
-                                                       int offset, int avail)
+                              int offset, int avail)
 {
        struct rproc_mem_entry *mapping;
        struct device *dev = &rproc->dev;
@@ -530,7 +524,7 @@ static int rproc_handle_devmem(struct rproc *rproc, struct fw_rsc_devmem *rsc,
        list_add_tail(&mapping->node, &rproc->mappings);
 
        dev_dbg(dev, "mapped devmem pa 0x%x, da 0x%x, len 0x%x\n",
-                                       rsc->pa, rsc->da, rsc->len);
+               rsc->pa, rsc->da, rsc->len);
 
        return 0;
 
@@ -558,9 +552,8 @@ out:
  * pressure is important; it may have a substantial impact on performance.
  */
 static int rproc_handle_carveout(struct rproc *rproc,
-                                               struct fw_rsc_carveout *rsc,
-                                               int offset, int avail)
-
+                                struct fw_rsc_carveout *rsc,
+                                int offset, int avail)
 {
        struct rproc_mem_entry *carveout, *mapping;
        struct device *dev = &rproc->dev;
@@ -579,8 +572,8 @@ static int rproc_handle_carveout(struct rproc *rproc,
                return -EINVAL;
        }
 
-       dev_dbg(dev, "carveout rsc: da %x, pa %x, len %x, flags %x\n",
-                       rsc->da, rsc->pa, rsc->len, rsc->flags);
+       dev_dbg(dev, "carveout rsc: name: %s, da 0x%x, pa 0x%x, len 0x%x, flags 0x%x\n",
+               rsc->name, rsc->da, rsc->pa, rsc->len, rsc->flags);
 
        carveout = kzalloc(sizeof(*carveout), GFP_KERNEL);
        if (!carveout)
@@ -588,13 +581,14 @@ static int rproc_handle_carveout(struct rproc *rproc,
 
        va = dma_alloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
        if (!va) {
-               dev_err(dev->parent, "dma_alloc_coherent err: %d\n", rsc->len);
+               dev_err(dev->parent,
+                       "failed to allocate dma memory: len 0x%x\n", rsc->len);
                ret = -ENOMEM;
                goto free_carv;
        }
 
-       dev_dbg(dev, "carveout va %p, dma %llx, len 0x%x\n", va,
-                                       (unsigned long long)dma, rsc->len);
+       dev_dbg(dev, "carveout va %p, dma %pad, len 0x%x\n",
+               va, &dma, rsc->len);
 
        /*
         * Ok, this is non-standard.
@@ -616,13 +610,12 @@ static int rproc_handle_carveout(struct rproc *rproc,
        if (rproc->domain) {
                mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
                if (!mapping) {
-                       dev_err(dev, "kzalloc mapping failed\n");
                        ret = -ENOMEM;
                        goto dma_free;
                }
 
                ret = iommu_map(rproc->domain, rsc->da, dma, rsc->len,
-                                                               rsc->flags);
+                               rsc->flags);
                if (ret) {
                        dev_err(dev, "iommu_map failed: %d\n", ret);
                        goto free_mapping;
@@ -639,8 +632,8 @@ static int rproc_handle_carveout(struct rproc *rproc,
                mapping->len = rsc->len;
                list_add_tail(&mapping->node, &rproc->mappings);
 
-               dev_dbg(dev, "carveout mapped 0x%x to 0x%llx\n",
-                                       rsc->da, (unsigned long long)dma);
+               dev_dbg(dev, "carveout mapped 0x%x to %pad\n",
+                       rsc->da, &dma);
        }
 
        /*
@@ -697,17 +690,13 @@ static rproc_handle_resource_t rproc_loading_handlers[RSC_LAST] = {
        [RSC_CARVEOUT] = (rproc_handle_resource_t)rproc_handle_carveout,
        [RSC_DEVMEM] = (rproc_handle_resource_t)rproc_handle_devmem,
        [RSC_TRACE] = (rproc_handle_resource_t)rproc_handle_trace,
-       [RSC_VDEV] = NULL, /* VDEVs were handled upon registrarion */
+       [RSC_VDEV] = (rproc_handle_resource_t)rproc_count_vrings,
 };
 
 static rproc_handle_resource_t rproc_vdev_handler[RSC_LAST] = {
        [RSC_VDEV] = (rproc_handle_resource_t)rproc_handle_vdev,
 };
 
-static rproc_handle_resource_t rproc_count_vrings_handler[RSC_LAST] = {
-       [RSC_VDEV] = (rproc_handle_resource_t)rproc_count_vrings,
-};
-
 /* handle firmware resource entries before booting the remote processor */
 static int rproc_handle_resources(struct rproc *rproc, int len,
                                  rproc_handle_resource_t handlers[RSC_LAST])
@@ -757,6 +746,7 @@ static int rproc_handle_resources(struct rproc *rproc, int len,
 static void rproc_resource_cleanup(struct rproc *rproc)
 {
        struct rproc_mem_entry *entry, *tmp;
+       struct rproc_vdev *rvdev, *rvtmp;
        struct device *dev = &rproc->dev;
 
        /* clean up debugfs trace entries */
@@ -775,7 +765,7 @@ static void rproc_resource_cleanup(struct rproc *rproc)
                if (unmapped != entry->len) {
                        /* nothing much to do besides complaining */
                        dev_err(dev, "failed to unmap %u/%zu\n", entry->len,
-                                                               unmapped);
+                               unmapped);
                }
 
                list_del(&entry->node);
@@ -789,6 +779,10 @@ static void rproc_resource_cleanup(struct rproc *rproc)
                list_del(&entry->node);
                kfree(entry);
        }
+
+       /* clean up remote vdev entries */
+       list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
+               rproc_remove_virtio_dev(rvdev);
 }
 
 /*
@@ -801,9 +795,6 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
        struct resource_table *table, *loaded_table;
        int ret, tablesz;
 
-       if (!rproc->table_ptr)
-               return -ENOMEM;
-
        ret = rproc_fw_sanity_check(rproc, fw);
        if (ret)
                return ret;
@@ -830,9 +821,25 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
                goto clean_up;
        }
 
-       /* Verify that resource table in loaded fw is unchanged */
-       if (rproc->table_csum != crc32(0, table, tablesz)) {
-               dev_err(dev, "resource checksum failed, fw changed?\n");
+       /*
+        * Create a copy of the resource table. When a virtio device starts
+        * and calls vring_new_virtqueue() the address of the allocated vring
+        * will be stored in the cached_table. Before the device is started,
+        * cached_table will be copied into device memory.
+        */
+       rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL);
+       if (!rproc->cached_table)
+               goto clean_up;
+
+       rproc->table_ptr = rproc->cached_table;
+
+       /* reset max_notifyid */
+       rproc->max_notifyid = -1;
+
+       /* look for virtio devices and register them */
+       ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler);
+       if (ret) {
+               dev_err(dev, "Failed to handle vdev resources: %d\n", ret);
                goto clean_up;
        }
 
@@ -854,12 +861,15 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
         * The starting device has been given the rproc->cached_table as the
         * resource table. The address of the vring along with the other
         * allocated resources (carveouts etc) is stored in cached_table.
-        * In order to pass this information to the remote device we must
-        * copy this information to device memory.
+        * In order to pass this information to the remote device we must copy
+        * this information to device memory. We also update the table_ptr so
+        * that any subsequent changes will be applied to the loaded version.
         */
        loaded_table = rproc_find_loaded_rsc_table(rproc, fw);
-       if (loaded_table)
+       if (loaded_table) {
                memcpy(loaded_table, rproc->cached_table, tablesz);
+               rproc->table_ptr = loaded_table;
+       }
 
        /* power up the remote processor */
        ret = rproc->ops->start(rproc);
@@ -868,13 +878,6 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
                goto clean_up;
        }
 
-       /*
-        * Update table_ptr so that all subsequent vring allocations and
-        * virtio fields manipulation update the actual loaded resource table
-        * in device memory.
-        */
-       rproc->table_ptr = loaded_table;
-
        rproc->state = RPROC_RUNNING;
 
        dev_info(dev, "remote processor %s is now up\n", rproc->name);
@@ -882,6 +885,10 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
        return 0;
 
 clean_up:
+       kfree(rproc->cached_table);
+       rproc->cached_table = NULL;
+       rproc->table_ptr = NULL;
+
        rproc_resource_cleanup(rproc);
        rproc_disable_iommu(rproc);
        return ret;
@@ -898,42 +905,11 @@ clean_up:
 static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
 {
        struct rproc *rproc = context;
-       struct resource_table *table;
-       int ret, tablesz;
 
-       if (rproc_fw_sanity_check(rproc, fw) < 0)
-               goto out;
+       /* if rproc is marked always-on, request it to boot */
+       if (rproc->auto_boot)
+               rproc_boot_nowait(rproc);
 
-       /* look for the resource table */
-       table = rproc_find_rsc_table(rproc, fw,  &tablesz);
-       if (!table)
-               goto out;
-
-       rproc->table_csum = crc32(0, table, tablesz);
-
-       /*
-        * Create a copy of the resource table. When a virtio device starts
-        * and calls vring_new_virtqueue() the address of the allocated vring
-        * will be stored in the cached_table. Before the device is started,
-        * cached_table will be copied into devic memory.
-        */
-       rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL);
-       if (!rproc->cached_table)
-               goto out;
-
-       rproc->table_ptr = rproc->cached_table;
-
-       /* count the number of notify-ids */
-       rproc->max_notifyid = -1;
-       ret = rproc_handle_resources(rproc, tablesz,
-                                    rproc_count_vrings_handler);
-       if (ret)
-               goto out;
-
-       /* look for virtio devices and register them */
-       ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler);
-
-out:
        release_firmware(fw);
        /* allow rproc_del() contexts, if any, to proceed */
        complete_all(&rproc->firmware_loading_complete);
@@ -969,7 +945,7 @@ static int rproc_add_virtio_devices(struct rproc *rproc)
  * rproc_trigger_recovery() - recover a remoteproc
  * @rproc: the remote processor
  *
- * The recovery is done by reseting all the virtio devices, that way all the
+ * The recovery is done by resetting all the virtio devices, that way all the
  * rpmsg drivers will be reseted along with the remote processor making the
  * remoteproc functional again.
  *
@@ -977,23 +953,23 @@ static int rproc_add_virtio_devices(struct rproc *rproc)
  */
 int rproc_trigger_recovery(struct rproc *rproc)
 {
-       struct rproc_vdev *rvdev, *rvtmp;
-
        dev_err(&rproc->dev, "recovering %s\n", rproc->name);
 
        init_completion(&rproc->crash_comp);
 
-       /* clean up remote vdev entries */
-       list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node)
-               rproc_remove_virtio_dev(rvdev);
+       /* shut down the remote */
+       /* TODO: make sure this works with rproc->power > 1 */
+       rproc_shutdown(rproc);
 
        /* wait until there is no more rproc users */
        wait_for_completion(&rproc->crash_comp);
 
-       /* Free the copy of the resource table */
-       kfree(rproc->cached_table);
+       /*
+        * boot the remote processor up again
+        */
+       rproc_boot(rproc);
 
-       return rproc_add_virtio_devices(rproc);
+       return 0;
 }
 
 /**
@@ -1173,8 +1149,10 @@ void rproc_shutdown(struct rproc *rproc)
 
        rproc_disable_iommu(rproc);
 
-       /* Give the next start a clean resource table */
-       rproc->table_ptr = rproc->cached_table;
+       /* Free the copy of the resource table */
+       kfree(rproc->cached_table);
+       rproc->cached_table = NULL;
+       rproc->table_ptr = NULL;
 
        /* if in crash state, unlock crash handler */
        if (rproc->state == RPROC_CRASHED)
@@ -1338,8 +1316,8 @@ static struct device_type rproc_type = {
  * yet. Instead, when you need to unroll rproc_alloc(), use rproc_put().
  */
 struct rproc *rproc_alloc(struct device *dev, const char *name,
-                               const struct rproc_ops *ops,
-                               const char *firmware, int len)
+                         const struct rproc_ops *ops,
+                         const char *firmware, int len)
 {
        struct rproc *rproc;
        char *p, *template = "rproc-%s-fw";
@@ -1359,7 +1337,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
                 */
                name_len = strlen(name) + strlen(template) - 2 + 1;
 
-       rproc = kzalloc(sizeof(struct rproc) + len + name_len, GFP_KERNEL);
+       rproc = kzalloc(sizeof(*rproc) + len + name_len, GFP_KERNEL);
        if (!rproc)
                return NULL;
 
@@ -1374,6 +1352,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
        rproc->name = name;
        rproc->ops = ops;
        rproc->priv = &rproc[1];
+       rproc->auto_boot = true;
 
        device_initialize(&rproc->dev);
        rproc->dev.parent = dev;
@@ -1452,13 +1431,15 @@ int rproc_del(struct rproc *rproc)
        /* if rproc is just being registered, wait */
        wait_for_completion(&rproc->firmware_loading_complete);
 
+       /* if rproc is marked always-on, rproc_add() booted it */
+       /* TODO: make sure this works with rproc->power > 1 */
+       if (rproc->auto_boot)
+               rproc_shutdown(rproc);
+
        /* clean up remote vdev entries */
        list_for_each_entry_safe(rvdev, tmp, &rproc->rvdevs, node)
                rproc_remove_virtio_dev(rvdev);
 
-       /* Free the copy of the resource table */
-       kfree(rproc->cached_table);
-
        /* the rproc is downref'ed as soon as it's removed from the klist */
        mutex_lock(&rproc_list_mutex);
        list_del(&rproc->node);
index 74a120b6e206ec728093e8c532d2f757722f267f..374797206c79032ee854219a7fbb6fee5fe87066 100644 (file)
@@ -45,7 +45,7 @@ static struct dentry *rproc_dbg;
  * as it provides very early tracing with little to no dependencies at all.
  */
 static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf,
-                                               size_t count, loff_t *ppos)
+                               size_t count, loff_t *ppos)
 {
        struct rproc_mem_entry *trace = filp->private_data;
        int len = strnlen(trace->va, trace->len);
@@ -73,7 +73,7 @@ static const char * const rproc_state_string[] = {
 
 /* expose the state of the remote processor via debugfs */
 static ssize_t rproc_state_read(struct file *filp, char __user *userbuf,
-                                               size_t count, loff_t *ppos)
+                               size_t count, loff_t *ppos)
 {
        struct rproc *rproc = filp->private_data;
        unsigned int state;
@@ -83,7 +83,7 @@ static ssize_t rproc_state_read(struct file *filp, char __user *userbuf,
        state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
 
        i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
-                                                       rproc->state);
+                     rproc->state);
 
        return simple_read_from_buffer(userbuf, count, ppos, buf, i);
 }
@@ -130,7 +130,7 @@ static const struct file_operations rproc_state_ops = {
 
 /* expose the name of the remote processor via debugfs */
 static ssize_t rproc_name_read(struct file *filp, char __user *userbuf,
-                                               size_t count, loff_t *ppos)
+                              size_t count, loff_t *ppos)
 {
        struct rproc *rproc = filp->private_data;
        /* need room for the name, a newline and a terminating null */
@@ -230,12 +230,12 @@ void rproc_remove_trace_file(struct dentry *tfile)
 }
 
 struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
-                                       struct rproc_mem_entry *trace)
+                                      struct rproc_mem_entry *trace)
 {
        struct dentry *tfile;
 
-       tfile = debugfs_create_file(name, 0400, rproc->dbg_dir,
-                                               trace, &trace_rproc_ops);
+       tfile = debugfs_create_file(name, 0400, rproc->dbg_dir, trace,
+                                   &trace_rproc_ops);
        if (!tfile) {
                dev_err(&rproc->dev, "failed to create debugfs trace entry\n");
                return NULL;
@@ -264,11 +264,11 @@ void rproc_create_debug_dir(struct rproc *rproc)
                return;
 
        debugfs_create_file("name", 0400, rproc->dbg_dir,
-                                       rproc, &rproc_name_ops);
+                           rproc, &rproc_name_ops);
        debugfs_create_file("state", 0400, rproc->dbg_dir,
-                                       rproc, &rproc_state_ops);
+                           rproc, &rproc_state_ops);
        debugfs_create_file("recovery", 0400, rproc->dbg_dir,
-                                       rproc, &rproc_recovery_ops);
+                           rproc, &rproc_recovery_ops);
 }
 
 void __init rproc_init_debugfs(void)
index ce283a5b42a1e2b677133b9ee2d6284fb80625a7..c523983a4aec483aecad95bce12a04f381885ead 100644 (file)
@@ -166,18 +166,18 @@ rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
                        continue;
 
                dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
-                                       phdr->p_type, da, memsz, filesz);
+                       phdr->p_type, da, memsz, filesz);
 
                if (filesz > memsz) {
                        dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
-                                                       filesz, memsz);
+                               filesz, memsz);
                        ret = -EINVAL;
                        break;
                }
 
                if (offset + filesz > fw->size) {
                        dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n",
-                                       offset + filesz, fw->size);
+                               offset + filesz, fw->size);
                        ret = -EINVAL;
                        break;
                }
index 57e1de59bec8df168a258488a309a17b1aaca66e..4cf93ca2816e29b8ac7a87bf6904020f05a8f079 100644 (file)
@@ -36,10 +36,10 @@ struct rproc;
  */
 struct rproc_fw_ops {
        struct resource_table *(*find_rsc_table)(struct rproc *rproc,
-                                               const struct firmware *fw,
-                                               int *tablesz);
-       struct resource_table *(*find_loaded_rsc_table)(struct rproc *rproc,
-                                               const struct firmware *fw);
+                                                const struct firmware *fw,
+                                                int *tablesz);
+       struct resource_table *(*find_loaded_rsc_table)(
+                               struct rproc *rproc, const struct firmware *fw);
        int (*load)(struct rproc *rproc, const struct firmware *fw);
        int (*sanity_check)(struct rproc *rproc, const struct firmware *fw);
        u32 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw);
@@ -57,7 +57,7 @@ void rproc_remove_virtio_dev(struct rproc_vdev *rvdev);
 /* from remoteproc_debugfs.c */
 void rproc_remove_trace_file(struct dentry *tfile);
 struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
-                                       struct rproc_mem_entry *trace);
+                                      struct rproc_mem_entry *trace);
 void rproc_delete_debug_dir(struct rproc *rproc);
 void rproc_create_debug_dir(struct rproc *rproc);
 void rproc_init_debugfs(void);
@@ -98,7 +98,8 @@ int rproc_load_segments(struct rproc *rproc, const struct firmware *fw)
 
 static inline
 struct resource_table *rproc_find_rsc_table(struct rproc *rproc,
-                               const struct firmware *fw, int *tablesz)
+                                           const struct firmware *fw,
+                                           int *tablesz)
 {
        if (rproc->fw_ops->find_rsc_table)
                return rproc->fw_ops->find_rsc_table(rproc, fw, tablesz);
@@ -108,7 +109,7 @@ struct resource_table *rproc_find_rsc_table(struct rproc *rproc,
 
 static inline
 struct resource_table *rproc_find_loaded_rsc_table(struct rproc *rproc,
-                                               const struct firmware *fw)
+                                                  const struct firmware *fw)
 {
        if (rproc->fw_ops->find_loaded_rsc_table)
                return rproc->fw_ops->find_loaded_rsc_table(rproc, fw);
index cc91556313e1763073a42b47791a77aae6728a60..01870a16d6d23e7023d511da3d27a1695899597a 100644 (file)
@@ -69,7 +69,7 @@ irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
 EXPORT_SYMBOL(rproc_vq_interrupt);
 
 static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
-                                   unsigned id,
+                                   unsigned int id,
                                    void (*callback)(struct virtqueue *vq),
                                    const char *name)
 {
@@ -101,14 +101,14 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
        memset(addr, 0, size);
 
        dev_dbg(dev, "vring%d: va %p qsz %d notifyid %d\n",
-                                       id, addr, len, rvring->notifyid);
+               id, addr, len, rvring->notifyid);
 
        /*
         * Create the new vq, and tell virtio we're not interested in
         * the 'weak' smp barriers, since we're talking with a real device.
         */
        vq = vring_new_virtqueue(id, len, rvring->align, vdev, false, addr,
-                                       rproc_virtio_notify, callback, name);
+                                rproc_virtio_notify, callback, name);
        if (!vq) {
                dev_err(dev, "vring_new_virtqueue %s failed\n", name);
                rproc_free_vring(rvring);
@@ -136,20 +136,14 @@ static void __rproc_virtio_del_vqs(struct virtio_device *vdev)
 
 static void rproc_virtio_del_vqs(struct virtio_device *vdev)
 {
-       struct rproc *rproc = vdev_to_rproc(vdev);
-
-       /* power down the remote processor before deleting vqs */
-       rproc_shutdown(rproc);
-
        __rproc_virtio_del_vqs(vdev);
 }
 
-static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
-                      struct virtqueue *vqs[],
-                      vq_callback_t *callbacks[],
-                      const char * const names[])
+static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
+                                struct virtqueue *vqs[],
+                                vq_callback_t *callbacks[],
+                                const char * const names[])
 {
-       struct rproc *rproc = vdev_to_rproc(vdev);
        int i, ret;
 
        for (i = 0; i < nvqs; ++i) {
@@ -160,13 +154,6 @@ static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
                }
        }
 
-       /* now that the vqs are all set, boot the remote processor */
-       ret = rproc_boot_nowait(rproc);
-       if (ret) {
-               dev_err(&rproc->dev, "rproc_boot() failed %d\n", ret);
-               goto error;
-       }
-
        return 0;
 
 error:
@@ -239,8 +226,8 @@ static int rproc_virtio_finalize_features(struct virtio_device *vdev)
        return 0;
 }
 
-static void rproc_virtio_get(struct virtio_device *vdev, unsigned offset,
-                                                       void *buf, unsigned len)
+static void rproc_virtio_get(struct virtio_device *vdev, unsigned int offset,
+                            void *buf, unsigned int len)
 {
        struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
        struct fw_rsc_vdev *rsc;
@@ -257,8 +244,8 @@ static void rproc_virtio_get(struct virtio_device *vdev, unsigned offset,
        memcpy(buf, cfg + offset, len);
 }
 
-static void rproc_virtio_set(struct virtio_device *vdev, unsigned offset,
-                     const void *buf, unsigned len)
+static void rproc_virtio_set(struct virtio_device *vdev, unsigned int offset,
+                            const void *buf, unsigned int len)
 {
        struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
        struct fw_rsc_vdev *rsc;
index 02d271d101b45a0c6585d7dfe24f1e22259f88ed..3811cb522af35671fce9c92a112646e0182b9555 100644 (file)
@@ -167,6 +167,8 @@ static int wkup_m3_rproc_probe(struct platform_device *pdev)
                goto err;
        }
 
+       rproc->auto_boot = false;
+
        wkupm3 = rproc->priv;
        wkupm3->rproc = rproc;
        wkupm3->pdev = pdev;
index 69a219387582bf3391f6ecd7075e037769d0de9c..de31c5f14dd98b72e353f75ea1ad5f35843ed20a 100644 (file)
@@ -3,6 +3,20 @@ menu "Rpmsg drivers"
 # RPMSG always gets selected by whoever wants it
 config RPMSG
        tristate
+
+config RPMSG_QCOM_SMD
+       tristate "Qualcomm Shared Memory Driver (SMD)"
+       depends on QCOM_SMEM
+       depends on QCOM_SMD=n
+       select RPMSG
+       help
+         Say y here to enable support for the Qualcomm Shared Memory Driver
+         providing communication channels to remote processors in Qualcomm
+         platforms.
+
+config RPMSG_VIRTIO
+       tristate
+       select RPMSG
        select VIRTIO
        select VIRTUALIZATION
 
index 7617fcb8259f4836d1383fab3c4798c62c0d2e16..ae9c9132cf7667523336bc502fc9a9202ed3fe55 100644 (file)
@@ -1 +1,3 @@
-obj-$(CONFIG_RPMSG)    += virtio_rpmsg_bus.o
+obj-$(CONFIG_RPMSG)            += rpmsg_core.o
+obj-$(CONFIG_RPMSG_QCOM_SMD)   += qcom_smd.o
+obj-$(CONFIG_RPMSG_VIRTIO)     += virtio_rpmsg_bus.o
diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c
new file mode 100644 (file)
index 0000000..06fef2b
--- /dev/null
@@ -0,0 +1,1434 @@
+/*
+ * Copyright (c) 2015, Sony Mobile Communications AB.
+ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/wait.h>
+#include <linux/rpmsg.h>
+
+#include "rpmsg_internal.h"
+
+/*
+ * The Qualcomm Shared Memory communication solution provides point-to-point
+ * channels for clients to send and receive streaming or packet based data.
+ *
+ * Each channel consists of a control item (channel info) and a ring buffer
+ * pair. The channel info carry information related to channel state, flow
+ * control and the offsets within the ring buffer.
+ *
+ * All allocated channels are listed in an allocation table, identifying the
+ * pair of items by name, type and remote processor.
+ *
+ * Upon creating a new channel the remote processor allocates channel info and
+ * ring buffer items from the smem heap and populate the allocation table. An
+ * interrupt is sent to the other end of the channel and a scan for new
+ * channels should be done. A channel never goes away, it will only change
+ * state.
+ *
+ * The remote processor signals it intent for bring up the communication
+ * channel by setting the state of its end of the channel to "opening" and
+ * sends out an interrupt. We detect this change and register a smd device to
+ * consume the channel. Upon finding a consumer we finish the handshake and the
+ * channel is up.
+ *
+ * Upon closing a channel, the remote processor will update the state of its
+ * end of the channel and signal us, we will then unregister any attached
+ * device and close our end of the channel.
+ *
+ * Devices attached to a channel can use the qcom_smd_send function to push
+ * data to the channel, this is done by copying the data into the tx ring
+ * buffer, updating the pointers in the channel info and signaling the remote
+ * processor.
+ *
+ * The remote processor does the equivalent when it transfer data and upon
+ * receiving the interrupt we check the channel info for new data and delivers
+ * this to the attached device. If the device is not ready to receive the data
+ * we leave it in the ring buffer for now.
+ */
+
+struct smd_channel_info;
+struct smd_channel_info_pair;
+struct smd_channel_info_word;
+struct smd_channel_info_word_pair;
+
+static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops;
+
+#define SMD_ALLOC_TBL_COUNT    2
+#define SMD_ALLOC_TBL_SIZE     64
+
+/*
+ * This lists the various smem heap items relevant for the allocation table and
+ * smd channel entries.
+ */
+static const struct {
+       unsigned alloc_tbl_id;
+       unsigned info_base_id;
+       unsigned fifo_base_id;
+} smem_items[SMD_ALLOC_TBL_COUNT] = {
+       {
+               .alloc_tbl_id = 13,
+               .info_base_id = 14,
+               .fifo_base_id = 338
+       },
+       {
+               .alloc_tbl_id = 266,
+               .info_base_id = 138,
+               .fifo_base_id = 202,
+       },
+};
+
+/**
+ * struct qcom_smd_edge - representing a remote processor
+ * @of_node:           of_node handle for information related to this edge
+ * @edge_id:           identifier of this edge
+ * @remote_pid:                identifier of remote processor
+ * @irq:               interrupt for signals on this edge
+ * @ipc_regmap:                regmap handle holding the outgoing ipc register
+ * @ipc_offset:                offset within @ipc_regmap of the register for ipc
+ * @ipc_bit:           bit in the register at @ipc_offset of @ipc_regmap
+ * @channels:          list of all channels detected on this edge
+ * @channels_lock:     guard for modifications of @channels
+ * @allocated:         array of bitmaps representing already allocated channels
+ * @smem_available:    last available amount of smem triggering a channel scan
+ * @scan_work:         work item for discovering new channels
+ * @state_work:                work item for edge state changes
+ */
+struct qcom_smd_edge {
+       struct device dev;
+
+       struct device_node *of_node;
+       unsigned edge_id;
+       unsigned remote_pid;
+
+       int irq;
+
+       struct regmap *ipc_regmap;
+       int ipc_offset;
+       int ipc_bit;
+
+       struct list_head channels;
+       spinlock_t channels_lock;
+
+       DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE);
+
+       unsigned smem_available;
+
+       wait_queue_head_t new_channel_event;
+
+       struct work_struct scan_work;
+       struct work_struct state_work;
+};
+
+/*
+ * SMD channel states.
+ */
+enum smd_channel_state {
+       SMD_CHANNEL_CLOSED,
+       SMD_CHANNEL_OPENING,
+       SMD_CHANNEL_OPENED,
+       SMD_CHANNEL_FLUSHING,
+       SMD_CHANNEL_CLOSING,
+       SMD_CHANNEL_RESET,
+       SMD_CHANNEL_RESET_OPENING
+};
+
+struct qcom_smd_device {
+       struct rpmsg_device rpdev;
+
+       struct qcom_smd_edge *edge;
+};
+
+struct qcom_smd_endpoint {
+       struct rpmsg_endpoint ept;
+
+       struct qcom_smd_channel *qsch;
+};
+
+#define to_smd_device(_rpdev)  container_of(_rpdev, struct qcom_smd_device, rpdev)
+#define to_smd_edge(d)         container_of(d, struct qcom_smd_edge, dev)
+#define to_smd_endpoint(ept)   container_of(ept, struct qcom_smd_endpoint, ept)
+
+/**
+ * struct qcom_smd_channel - smd channel struct
+ * @edge:              qcom_smd_edge this channel is living on
+ * @qsdev:             reference to a associated smd client device
+ * @name:              name of the channel
+ * @state:             local state of the channel
+ * @remote_state:      remote state of the channel
+ * @info:              byte aligned outgoing/incoming channel info
+ * @info_word:         word aligned outgoing/incoming channel info
+ * @tx_lock:           lock to make writes to the channel mutually exclusive
+ * @fblockread_event:  wakeup event tied to tx fBLOCKREADINTR
+ * @tx_fifo:           pointer to the outgoing ring buffer
+ * @rx_fifo:           pointer to the incoming ring buffer
+ * @fifo_size:         size of each ring buffer
+ * @bounce_buffer:     bounce buffer for reading wrapped packets
+ * @cb:                        callback function registered for this channel
+ * @recv_lock:         guard for rx info modifications and cb pointer
+ * @pkt_size:          size of the currently handled packet
+ * @list:              lite entry for @channels in qcom_smd_edge
+ */
+struct qcom_smd_channel {
+       struct qcom_smd_edge *edge;
+
+       struct qcom_smd_endpoint *qsept;
+       bool registered;
+
+       char *name;
+       enum smd_channel_state state;
+       enum smd_channel_state remote_state;
+
+       struct smd_channel_info_pair *info;
+       struct smd_channel_info_word_pair *info_word;
+
+       struct mutex tx_lock;
+       wait_queue_head_t fblockread_event;
+
+       void *tx_fifo;
+       void *rx_fifo;
+       int fifo_size;
+
+       void *bounce_buffer;
+
+       spinlock_t recv_lock;
+
+       int pkt_size;
+
+       void *drvdata;
+
+       struct list_head list;
+};
+
+/*
+ * Format of the smd_info smem items, for byte aligned channels.
+ */
+struct smd_channel_info {
+       __le32 state;
+       u8  fDSR;
+       u8  fCTS;
+       u8  fCD;
+       u8  fRI;
+       u8  fHEAD;
+       u8  fTAIL;
+       u8  fSTATE;
+       u8  fBLOCKREADINTR;
+       __le32 tail;
+       __le32 head;
+};
+
+struct smd_channel_info_pair {
+       struct smd_channel_info tx;
+       struct smd_channel_info rx;
+};
+
+/*
+ * Format of the smd_info smem items, for word aligned channels.
+ */
+struct smd_channel_info_word {
+       __le32 state;
+       __le32 fDSR;
+       __le32 fCTS;
+       __le32 fCD;
+       __le32 fRI;
+       __le32 fHEAD;
+       __le32 fTAIL;
+       __le32 fSTATE;
+       __le32 fBLOCKREADINTR;
+       __le32 tail;
+       __le32 head;
+};
+
+struct smd_channel_info_word_pair {
+       struct smd_channel_info_word tx;
+       struct smd_channel_info_word rx;
+};
+
+#define GET_RX_CHANNEL_FLAG(channel, param)                                 \
+       ({                                                                   \
+               BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
+               channel->info_word ?                                         \
+                       le32_to_cpu(channel->info_word->rx.param) :          \
+                       channel->info->rx.param;                             \
+       })
+
+#define GET_RX_CHANNEL_INFO(channel, param)                                  \
+       ({                                                                    \
+               BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
+               le32_to_cpu(channel->info_word ?                              \
+                       channel->info_word->rx.param :                        \
+                       channel->info->rx.param);                             \
+       })
+
+#define SET_RX_CHANNEL_FLAG(channel, param, value)                          \
+       ({                                                                   \
+               BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \
+               if (channel->info_word)                                      \
+                       channel->info_word->rx.param = cpu_to_le32(value);   \
+               else                                                         \
+                       channel->info->rx.param = value;                     \
+       })
+
+#define SET_RX_CHANNEL_INFO(channel, param, value)                           \
+       ({                                                                    \
+               BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \
+               if (channel->info_word)                                       \
+                       channel->info_word->rx.param = cpu_to_le32(value);    \
+               else                                                          \
+                       channel->info->rx.param = cpu_to_le32(value);         \
+       })
+
+#define GET_TX_CHANNEL_FLAG(channel, param)                                 \
+       ({                                                                   \
+               BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
+               channel->info_word ?                                         \
+                       le32_to_cpu(channel->info_word->tx.param) :          \
+                       channel->info->tx.param;                             \
+       })
+
+#define GET_TX_CHANNEL_INFO(channel, param)                                  \
+       ({                                                                    \
+               BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
+               le32_to_cpu(channel->info_word ?                              \
+                       channel->info_word->tx.param :                        \
+                       channel->info->tx.param);                             \
+       })
+
+#define SET_TX_CHANNEL_FLAG(channel, param, value)                          \
+       ({                                                                   \
+               BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \
+               if (channel->info_word)                                      \
+                       channel->info_word->tx.param = cpu_to_le32(value);   \
+               else                                                         \
+                       channel->info->tx.param = value;                     \
+       })
+
+#define SET_TX_CHANNEL_INFO(channel, param, value)                           \
+       ({                                                                    \
+               BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \
+               if (channel->info_word)                                       \
+                       channel->info_word->tx.param = cpu_to_le32(value);   \
+               else                                                          \
+                       channel->info->tx.param = cpu_to_le32(value);         \
+       })
+
+/**
+ * struct qcom_smd_alloc_entry - channel allocation entry
+ * @name:      channel name
+ * @cid:       channel index
+ * @flags:     channel flags and edge id
+ * @ref_count: reference count of the channel
+ */
+struct qcom_smd_alloc_entry {
+       u8 name[20];
+       __le32 cid;
+       __le32 flags;
+       __le32 ref_count;
+} __packed;
+
+#define SMD_CHANNEL_FLAGS_EDGE_MASK    0xff
+#define SMD_CHANNEL_FLAGS_STREAM       BIT(8)
+#define SMD_CHANNEL_FLAGS_PACKET       BIT(9)
+
+/*
+ * Each smd packet contains a 20 byte header, with the first 4 being the length
+ * of the packet.
+ */
+#define SMD_PACKET_HEADER_LEN  20
+
+/*
+ * Signal the remote processor associated with 'channel'.
+ */
+static void qcom_smd_signal_channel(struct qcom_smd_channel *channel)
+{
+       struct qcom_smd_edge *edge = channel->edge;
+
+       regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit));
+}
+
+/*
+ * Initialize the tx channel info
+ */
+static void qcom_smd_channel_reset(struct qcom_smd_channel *channel)
+{
+       SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED);
+       SET_TX_CHANNEL_FLAG(channel, fDSR, 0);
+       SET_TX_CHANNEL_FLAG(channel, fCTS, 0);
+       SET_TX_CHANNEL_FLAG(channel, fCD, 0);
+       SET_TX_CHANNEL_FLAG(channel, fRI, 0);
+       SET_TX_CHANNEL_FLAG(channel, fHEAD, 0);
+       SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
+       SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
+       SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
+       SET_TX_CHANNEL_INFO(channel, head, 0);
+       SET_RX_CHANNEL_INFO(channel, tail, 0);
+
+       qcom_smd_signal_channel(channel);
+
+       channel->state = SMD_CHANNEL_CLOSED;
+       channel->pkt_size = 0;
+}
+
+/*
+ * Set the callback for a channel, with appropriate locking
+ */
+static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,
+                                         rpmsg_rx_cb_t cb)
+{
+       struct rpmsg_endpoint *ept = &channel->qsept->ept;
+       unsigned long flags;
+
+       spin_lock_irqsave(&channel->recv_lock, flags);
+       ept->cb = cb;
+       spin_unlock_irqrestore(&channel->recv_lock, flags);
+};
+
+/*
+ * Calculate the amount of data available in the rx fifo
+ */
+static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel)
+{
+       unsigned head;
+       unsigned tail;
+
+       head = GET_RX_CHANNEL_INFO(channel, head);
+       tail = GET_RX_CHANNEL_INFO(channel, tail);
+
+       return (head - tail) & (channel->fifo_size - 1);
+}
+
+/*
+ * Set tx channel state and inform the remote processor
+ */
+static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
+                                      int state)
+{
+       struct qcom_smd_edge *edge = channel->edge;
+       bool is_open = state == SMD_CHANNEL_OPENED;
+
+       if (channel->state == state)
+               return;
+
+       dev_dbg(&edge->dev, "set_state(%s, %d)\n", channel->name, state);
+
+       SET_TX_CHANNEL_FLAG(channel, fDSR, is_open);
+       SET_TX_CHANNEL_FLAG(channel, fCTS, is_open);
+       SET_TX_CHANNEL_FLAG(channel, fCD, is_open);
+
+       SET_TX_CHANNEL_INFO(channel, state, state);
+       SET_TX_CHANNEL_FLAG(channel, fSTATE, 1);
+
+       channel->state = state;
+       qcom_smd_signal_channel(channel);
+}
+
+/*
+ * Copy count bytes of data using 32bit accesses, if that's required.
+ */
+static void smd_copy_to_fifo(void __iomem *dst,
+                            const void *src,
+                            size_t count,
+                            bool word_aligned)
+{
+       if (word_aligned) {
+               __iowrite32_copy(dst, src, count / sizeof(u32));
+       } else {
+               memcpy_toio(dst, src, count);
+       }
+}
+
+/*
+ * Copy count bytes of data using 32bit accesses, if that is required.
+ */
+static void smd_copy_from_fifo(void *dst,
+                              const void __iomem *src,
+                              size_t count,
+                              bool word_aligned)
+{
+       if (word_aligned) {
+               __ioread32_copy(dst, src, count / sizeof(u32));
+       } else {
+               memcpy_fromio(dst, src, count);
+       }
+}
+
+/*
+ * Read count bytes of data from the rx fifo into buf, but don't advance the
+ * tail.
+ */
+static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
+                                   void *buf, size_t count)
+{
+       bool word_aligned;
+       unsigned tail;
+       size_t len;
+
+       word_aligned = channel->info_word;
+       tail = GET_RX_CHANNEL_INFO(channel, tail);
+
+       len = min_t(size_t, count, channel->fifo_size - tail);
+       if (len) {
+               smd_copy_from_fifo(buf,
+                                  channel->rx_fifo + tail,
+                                  len,
+                                  word_aligned);
+       }
+
+       if (len != count) {
+               smd_copy_from_fifo(buf + len,
+                                  channel->rx_fifo,
+                                  count - len,
+                                  word_aligned);
+       }
+
+       return count;
+}
+
+/*
+ * Advance the rx tail by count bytes.
+ */
+static void qcom_smd_channel_advance(struct qcom_smd_channel *channel,
+                                    size_t count)
+{
+       unsigned tail;
+
+       tail = GET_RX_CHANNEL_INFO(channel, tail);
+       tail += count;
+       tail &= (channel->fifo_size - 1);
+       SET_RX_CHANNEL_INFO(channel, tail, tail);
+}
+
+/*
+ * Read out a single packet from the rx fifo and deliver it to the device
+ */
+static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel)
+{
+       struct rpmsg_endpoint *ept = &channel->qsept->ept;
+       unsigned tail;
+       size_t len;
+       void *ptr;
+       int ret;
+
+       tail = GET_RX_CHANNEL_INFO(channel, tail);
+
+       /* Use bounce buffer if the data wraps */
+       if (tail + channel->pkt_size >= channel->fifo_size) {
+               ptr = channel->bounce_buffer;
+               len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size);
+       } else {
+               ptr = channel->rx_fifo + tail;
+               len = channel->pkt_size;
+       }
+
+       ret = ept->cb(ept->rpdev, ptr, len, ept->priv, RPMSG_ADDR_ANY);
+       if (ret < 0)
+               return ret;
+
+       /* Only forward the tail if the client consumed the data */
+       qcom_smd_channel_advance(channel, len);
+
+       channel->pkt_size = 0;
+
+       return 0;
+}
+
+/*
+ * Per channel interrupt handling
+ */
+static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel)
+{
+       bool need_state_scan = false;
+       int remote_state;
+       __le32 pktlen;
+       int avail;
+       int ret;
+
+       /* Handle state changes */
+       remote_state = GET_RX_CHANNEL_INFO(channel, state);
+       if (remote_state != channel->remote_state) {
+               channel->remote_state = remote_state;
+               need_state_scan = true;
+       }
+       /* Indicate that we have seen any state change */
+       SET_RX_CHANNEL_FLAG(channel, fSTATE, 0);
+
+       /* Signal waiting qcom_smd_send() about the interrupt */
+       if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR))
+               wake_up_interruptible(&channel->fblockread_event);
+
+       /* Don't consume any data until we've opened the channel */
+       if (channel->state != SMD_CHANNEL_OPENED)
+               goto out;
+
+       /* Indicate that we've seen the new data */
+       SET_RX_CHANNEL_FLAG(channel, fHEAD, 0);
+
+       /* Consume data */
+       for (;;) {
+               avail = qcom_smd_channel_get_rx_avail(channel);
+
+               if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) {
+                       qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen));
+                       qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN);
+                       channel->pkt_size = le32_to_cpu(pktlen);
+               } else if (channel->pkt_size && avail >= channel->pkt_size) {
+                       ret = qcom_smd_channel_recv_single(channel);
+                       if (ret)
+                               break;
+               } else {
+                       break;
+               }
+       }
+
+       /* Indicate that we have seen and updated tail */
+       SET_RX_CHANNEL_FLAG(channel, fTAIL, 1);
+
+       /* Signal the remote that we've consumed the data (if requested) */
+       if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) {
+               /* Ensure ordering of channel info updates */
+               wmb();
+
+               qcom_smd_signal_channel(channel);
+       }
+
+out:
+       return need_state_scan;
+}
+
+/*
+ * The edge interrupts are triggered by the remote processor on state changes,
+ * channel info updates or when new channels are created.
+ */
+static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
+{
+       struct qcom_smd_edge *edge = data;
+       struct qcom_smd_channel *channel;
+       unsigned available;
+       bool kick_scanner = false;
+       bool kick_state = false;
+
+       /*
+        * Handle state changes or data on each of the channels on this edge
+        */
+       spin_lock(&edge->channels_lock);
+       list_for_each_entry(channel, &edge->channels, list) {
+               spin_lock(&channel->recv_lock);
+               kick_state |= qcom_smd_channel_intr(channel);
+               spin_unlock(&channel->recv_lock);
+       }
+       spin_unlock(&edge->channels_lock);
+
+       /*
+        * Creating a new channel requires allocating an smem entry, so we only
+        * have to scan if the amount of available space in smem have changed
+        * since last scan.
+        */
+       available = qcom_smem_get_free_space(edge->remote_pid);
+       if (available != edge->smem_available) {
+               edge->smem_available = available;
+               kick_scanner = true;
+       }
+
+       if (kick_scanner)
+               schedule_work(&edge->scan_work);
+       if (kick_state)
+               schedule_work(&edge->state_work);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Calculate how much space is available in the tx fifo.
+ */
+static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel)
+{
+       unsigned head;
+       unsigned tail;
+       unsigned mask = channel->fifo_size - 1;
+
+       head = GET_TX_CHANNEL_INFO(channel, head);
+       tail = GET_TX_CHANNEL_INFO(channel, tail);
+
+       return mask - ((head - tail) & mask);
+}
+
+/*
+ * Write count bytes of data into channel, possibly wrapping in the ring buffer
+ */
+static int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
+                              const void *data,
+                              size_t count)
+{
+       bool word_aligned;
+       unsigned head;
+       size_t len;
+
+       word_aligned = channel->info_word;
+       head = GET_TX_CHANNEL_INFO(channel, head);
+
+       len = min_t(size_t, count, channel->fifo_size - head);
+       if (len) {
+               smd_copy_to_fifo(channel->tx_fifo + head,
+                                data,
+                                len,
+                                word_aligned);
+       }
+
+       if (len != count) {
+               smd_copy_to_fifo(channel->tx_fifo,
+                                data + len,
+                                count - len,
+                                word_aligned);
+       }
+
+       head += count;
+       head &= (channel->fifo_size - 1);
+       SET_TX_CHANNEL_INFO(channel, head, head);
+
+       return count;
+}
+
+/**
+ * qcom_smd_send - write data to smd channel
+ * @channel:   channel handle
+ * @data:      buffer of data to write
+ * @len:       number of bytes to write
+ *
+ * This is a blocking write of len bytes into the channel's tx ring buffer and
+ * signal the remote end. It will sleep until there is enough space available
+ * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid
+ * polling.
+ */
+static int __qcom_smd_send(struct qcom_smd_channel *channel, const void *data,
+                          int len, bool wait)
+{
+       __le32 hdr[5] = { cpu_to_le32(len), };
+       int tlen = sizeof(hdr) + len;
+       int ret;
+
+       /* Word aligned channels only accept word size aligned data */
+       if (channel->info_word && len % 4)
+               return -EINVAL;
+
+       /* Reject packets that are too big */
+       if (tlen >= channel->fifo_size)
+               return -EINVAL;
+
+       ret = mutex_lock_interruptible(&channel->tx_lock);
+       if (ret)
+               return ret;
+
+       while (qcom_smd_get_tx_avail(channel) < tlen) {
+               if (!wait) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               if (channel->state != SMD_CHANNEL_OPENED) {
+                       ret = -EPIPE;
+                       goto out;
+               }
+
+               SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0);
+
+               ret = wait_event_interruptible(channel->fblockread_event,
+                                      qcom_smd_get_tx_avail(channel) >= tlen ||
+                                      channel->state != SMD_CHANNEL_OPENED);
+               if (ret)
+                       goto out;
+
+               SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1);
+       }
+
+       SET_TX_CHANNEL_FLAG(channel, fTAIL, 0);
+
+       qcom_smd_write_fifo(channel, hdr, sizeof(hdr));
+       qcom_smd_write_fifo(channel, data, len);
+
+       SET_TX_CHANNEL_FLAG(channel, fHEAD, 1);
+
+       /* Ensure ordering of channel info updates */
+       wmb();
+
+       qcom_smd_signal_channel(channel);
+
+out:
+       mutex_unlock(&channel->tx_lock);
+
+       return ret;
+}
+
+/*
+ * Helper for opening a channel
+ */
+static int qcom_smd_channel_open(struct qcom_smd_channel *channel,
+                                rpmsg_rx_cb_t cb)
+{
+       size_t bb_size;
+
+       /*
+        * Packets are maximum 4k, but reduce if the fifo is smaller
+        */
+       bb_size = min(channel->fifo_size, SZ_4K);
+       channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL);
+       if (!channel->bounce_buffer)
+               return -ENOMEM;
+
+       qcom_smd_channel_set_callback(channel, cb);
+       qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING);
+       qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED);
+
+       return 0;
+}
+
+/*
+ * Helper for closing and resetting a channel
+ */
+static void qcom_smd_channel_close(struct qcom_smd_channel *channel)
+{
+       qcom_smd_channel_set_callback(channel, NULL);
+
+       kfree(channel->bounce_buffer);
+       channel->bounce_buffer = NULL;
+
+       qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
+       qcom_smd_channel_reset(channel);
+}
+
+static struct qcom_smd_channel *
+qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
+{
+       struct qcom_smd_channel *channel;
+       struct qcom_smd_channel *ret = NULL;
+       unsigned long flags;
+       unsigned state;
+
+       spin_lock_irqsave(&edge->channels_lock, flags);
+       list_for_each_entry(channel, &edge->channels, list) {
+               if (strcmp(channel->name, name))
+                       continue;
+
+               state = GET_RX_CHANNEL_INFO(channel, state);
+               if (state != SMD_CHANNEL_OPENING &&
+                   state != SMD_CHANNEL_OPENED)
+                       continue;
+
+               ret = channel;
+               break;
+       }
+       spin_unlock_irqrestore(&edge->channels_lock, flags);
+
+       return ret;
+}
+
+static void __ept_release(struct kref *kref)
+{
+       struct rpmsg_endpoint *ept = container_of(kref, struct rpmsg_endpoint,
+                                                 refcount);
+       kfree(to_smd_endpoint(ept));
+}
+
+static struct rpmsg_endpoint *qcom_smd_create_ept(struct rpmsg_device *rpdev,
+                                                 rpmsg_rx_cb_t cb, void *priv,
+                                                 struct rpmsg_channel_info chinfo)
+{
+       struct qcom_smd_endpoint *qsept;
+       struct qcom_smd_channel *channel;
+       struct qcom_smd_device *qsdev = to_smd_device(rpdev);
+       struct qcom_smd_edge *edge = qsdev->edge;
+       struct rpmsg_endpoint *ept;
+       const char *name = chinfo.name;
+       int ret;
+
+       /* Wait up to HZ for the channel to appear */
+       ret = wait_event_interruptible_timeout(edge->new_channel_event,
+                       (channel = qcom_smd_find_channel(edge, name)) != NULL,
+                       HZ);
+       if (!ret)
+               return NULL;
+
+       if (channel->state != SMD_CHANNEL_CLOSED) {
+               dev_err(&rpdev->dev, "channel %s is busy\n", channel->name);
+               return NULL;
+       }
+
+       qsept = kzalloc(sizeof(*qsept), GFP_KERNEL);
+       if (!qsept)
+               return NULL;
+
+       ept = &qsept->ept;
+
+       kref_init(&ept->refcount);
+
+       ept->rpdev = rpdev;
+       ept->cb = cb;
+       ept->priv = priv;
+       ept->ops = &qcom_smd_endpoint_ops;
+
+       channel->qsept = qsept;
+       qsept->qsch = channel;
+
+       ret = qcom_smd_channel_open(channel, cb);
+       if (ret)
+               goto free_ept;
+
+       return ept;
+
+free_ept:
+       channel->qsept = NULL;
+       kref_put(&ept->refcount, __ept_release);
+       return NULL;
+}
+
+static void qcom_smd_destroy_ept(struct rpmsg_endpoint *ept)
+{
+       struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
+       struct qcom_smd_channel *ch = qsept->qsch;
+
+       qcom_smd_channel_close(ch);
+       ch->qsept = NULL;
+       kref_put(&ept->refcount, __ept_release);
+}
+
+static int qcom_smd_send(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
+
+       return __qcom_smd_send(qsept->qsch, data, len, true);
+}
+
+static int qcom_smd_trysend(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       struct qcom_smd_endpoint *qsept = to_smd_endpoint(ept);
+
+       return __qcom_smd_send(qsept->qsch, data, len, false);
+}
+
+/*
+ * Finds the device_node for the smd child interested in this channel.
+ */
+static struct device_node *qcom_smd_match_channel(struct device_node *edge_node,
+                                                 const char *channel)
+{
+       struct device_node *child;
+       const char *name;
+       const char *key;
+       int ret;
+
+       for_each_available_child_of_node(edge_node, child) {
+               key = "qcom,smd-channels";
+               ret = of_property_read_string(child, key, &name);
+               if (ret)
+                       continue;
+
+               if (strcmp(name, channel) == 0)
+                       return child;
+       }
+
+       return NULL;
+}
+
+static const struct rpmsg_device_ops qcom_smd_device_ops = {
+       .create_ept = qcom_smd_create_ept,
+};
+
+static const struct rpmsg_endpoint_ops qcom_smd_endpoint_ops = {
+       .destroy_ept = qcom_smd_destroy_ept,
+       .send = qcom_smd_send,
+       .trysend = qcom_smd_trysend,
+};
+
+/*
+ * Create a smd client device for channel that is being opened.
+ */
+static int qcom_smd_create_device(struct qcom_smd_channel *channel)
+{
+       struct qcom_smd_device *qsdev;
+       struct rpmsg_device *rpdev;
+       struct qcom_smd_edge *edge = channel->edge;
+
+       dev_dbg(&edge->dev, "registering '%s'\n", channel->name);
+
+       qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
+       if (!qsdev)
+               return -ENOMEM;
+
+       /* Link qsdev to our SMD edge */
+       qsdev->edge = edge;
+
+       /* Assign callbacks for rpmsg_device */
+       qsdev->rpdev.ops = &qcom_smd_device_ops;
+
+       /* Assign public information to the rpmsg_device */
+       rpdev = &qsdev->rpdev;
+       strncpy(rpdev->id.name, channel->name, RPMSG_NAME_SIZE);
+       rpdev->src = RPMSG_ADDR_ANY;
+       rpdev->dst = RPMSG_ADDR_ANY;
+
+       rpdev->dev.of_node = qcom_smd_match_channel(edge->of_node, channel->name);
+       rpdev->dev.parent = &edge->dev;
+
+       return rpmsg_register_device(rpdev);
+}
+
+/*
+ * Allocate the qcom_smd_channel object for a newly found smd channel,
+ * retrieving and validating the smem items involved.
+ */
+static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge,
+                                                       unsigned smem_info_item,
+                                                       unsigned smem_fifo_item,
+                                                       char *name)
+{
+       struct qcom_smd_channel *channel;
+       size_t fifo_size;
+       size_t info_size;
+       void *fifo_base;
+       void *info;
+       int ret;
+
+       channel = devm_kzalloc(&edge->dev, sizeof(*channel), GFP_KERNEL);
+       if (!channel)
+               return ERR_PTR(-ENOMEM);
+
+       channel->edge = edge;
+       channel->name = devm_kstrdup(&edge->dev, name, GFP_KERNEL);
+       if (!channel->name)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_init(&channel->tx_lock);
+       spin_lock_init(&channel->recv_lock);
+       init_waitqueue_head(&channel->fblockread_event);
+
+       info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size);
+       if (IS_ERR(info)) {
+               ret = PTR_ERR(info);
+               goto free_name_and_channel;
+       }
+
+       /*
+        * Use the size of the item to figure out which channel info struct to
+        * use.
+        */
+       if (info_size == 2 * sizeof(struct smd_channel_info_word)) {
+               channel->info_word = info;
+       } else if (info_size == 2 * sizeof(struct smd_channel_info)) {
+               channel->info = info;
+       } else {
+               dev_err(&edge->dev,
+                       "channel info of size %zu not supported\n", info_size);
+               ret = -EINVAL;
+               goto free_name_and_channel;
+       }
+
+       fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size);
+       if (IS_ERR(fifo_base)) {
+               ret =  PTR_ERR(fifo_base);
+               goto free_name_and_channel;
+       }
+
+       /* The channel consist of a rx and tx fifo of equal size */
+       fifo_size /= 2;
+
+       dev_dbg(&edge->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
+                         name, info_size, fifo_size);
+
+       channel->tx_fifo = fifo_base;
+       channel->rx_fifo = fifo_base + fifo_size;
+       channel->fifo_size = fifo_size;
+
+       qcom_smd_channel_reset(channel);
+
+       return channel;
+
+free_name_and_channel:
+       devm_kfree(&edge->dev, channel->name);
+       devm_kfree(&edge->dev, channel);
+
+       return ERR_PTR(ret);
+}
+
+/*
+ * Scans the allocation table for any newly allocated channels, calls
+ * qcom_smd_create_channel() to create representations of these and add
+ * them to the edge's list of channels.
+ */
+static void qcom_channel_scan_worker(struct work_struct *work)
+{
+       struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work);
+       struct qcom_smd_alloc_entry *alloc_tbl;
+       struct qcom_smd_alloc_entry *entry;
+       struct qcom_smd_channel *channel;
+       unsigned long flags;
+       unsigned fifo_id;
+       unsigned info_id;
+       int tbl;
+       int i;
+       u32 eflags, cid;
+
+       for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) {
+               alloc_tbl = qcom_smem_get(edge->remote_pid,
+                                   smem_items[tbl].alloc_tbl_id, NULL);
+               if (IS_ERR(alloc_tbl))
+                       continue;
+
+               for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) {
+                       entry = &alloc_tbl[i];
+                       eflags = le32_to_cpu(entry->flags);
+                       if (test_bit(i, edge->allocated[tbl]))
+                               continue;
+
+                       if (entry->ref_count == 0)
+                               continue;
+
+                       if (!entry->name[0])
+                               continue;
+
+                       if (!(eflags & SMD_CHANNEL_FLAGS_PACKET))
+                               continue;
+
+                       if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id)
+                               continue;
+
+                       cid = le32_to_cpu(entry->cid);
+                       info_id = smem_items[tbl].info_base_id + cid;
+                       fifo_id = smem_items[tbl].fifo_base_id + cid;
+
+                       channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name);
+                       if (IS_ERR(channel))
+                               continue;
+
+                       spin_lock_irqsave(&edge->channels_lock, flags);
+                       list_add(&channel->list, &edge->channels);
+                       spin_unlock_irqrestore(&edge->channels_lock, flags);
+
+                       dev_dbg(&edge->dev, "new channel found: '%s'\n", channel->name);
+                       set_bit(i, edge->allocated[tbl]);
+
+                       wake_up_interruptible(&edge->new_channel_event);
+               }
+       }
+
+       schedule_work(&edge->state_work);
+}
+
+/*
+ * This per edge worker scans smem for any new channels and register these. It
+ * then scans all registered channels for state changes that should be handled
+ * by creating or destroying smd client devices for the registered channels.
+ *
+ * LOCKING: edge->channels_lock only needs to cover the list operations, as the
+ * worker is killed before any channels are deallocated
+ */
+static void qcom_channel_state_worker(struct work_struct *work)
+{
+       struct qcom_smd_channel *channel;
+       struct qcom_smd_edge *edge = container_of(work,
+                                                 struct qcom_smd_edge,
+                                                 state_work);
+       struct rpmsg_channel_info chinfo;
+       unsigned remote_state;
+       unsigned long flags;
+
+       /*
+        * Register a device for any closed channel where the remote processor
+        * is showing interest in opening the channel.
+        */
+       spin_lock_irqsave(&edge->channels_lock, flags);
+       list_for_each_entry(channel, &edge->channels, list) {
+               if (channel->state != SMD_CHANNEL_CLOSED)
+                       continue;
+
+               remote_state = GET_RX_CHANNEL_INFO(channel, state);
+               if (remote_state != SMD_CHANNEL_OPENING &&
+                   remote_state != SMD_CHANNEL_OPENED)
+                       continue;
+
+               if (channel->registered)
+                       continue;
+
+               spin_unlock_irqrestore(&edge->channels_lock, flags);
+               qcom_smd_create_device(channel);
+               channel->registered = true;
+               spin_lock_irqsave(&edge->channels_lock, flags);
+
+               channel->registered = true;
+       }
+
+       /*
+        * Unregister the device for any channel that is opened where the
+        * remote processor is closing the channel.
+        */
+       list_for_each_entry(channel, &edge->channels, list) {
+               if (channel->state != SMD_CHANNEL_OPENING &&
+                   channel->state != SMD_CHANNEL_OPENED)
+                       continue;
+
+               remote_state = GET_RX_CHANNEL_INFO(channel, state);
+               if (remote_state == SMD_CHANNEL_OPENING ||
+                   remote_state == SMD_CHANNEL_OPENED)
+                       continue;
+
+               spin_unlock_irqrestore(&edge->channels_lock, flags);
+
+               strncpy(chinfo.name, channel->name, sizeof(chinfo.name));
+               chinfo.src = RPMSG_ADDR_ANY;
+               chinfo.dst = RPMSG_ADDR_ANY;
+               rpmsg_unregister_device(&edge->dev, &chinfo);
+               channel->registered = false;
+               spin_lock_irqsave(&edge->channels_lock, flags);
+       }
+       spin_unlock_irqrestore(&edge->channels_lock, flags);
+}
+
+/*
+ * Parses an of_node describing an edge.
+ */
+static int qcom_smd_parse_edge(struct device *dev,
+                              struct device_node *node,
+                              struct qcom_smd_edge *edge)
+{
+       struct device_node *syscon_np;
+       const char *key;
+       int irq;
+       int ret;
+
+       INIT_LIST_HEAD(&edge->channels);
+       spin_lock_init(&edge->channels_lock);
+
+       INIT_WORK(&edge->scan_work, qcom_channel_scan_worker);
+       INIT_WORK(&edge->state_work, qcom_channel_state_worker);
+
+       edge->of_node = of_node_get(node);
+
+       key = "qcom,smd-edge";
+       ret = of_property_read_u32(node, key, &edge->edge_id);
+       if (ret) {
+               dev_err(dev, "edge missing %s property\n", key);
+               return -EINVAL;
+       }
+
+       edge->remote_pid = QCOM_SMEM_HOST_ANY;
+       key = "qcom,remote-pid";
+       of_property_read_u32(node, key, &edge->remote_pid);
+
+       syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
+       if (!syscon_np) {
+               dev_err(dev, "no qcom,ipc node\n");
+               return -ENODEV;
+       }
+
+       edge->ipc_regmap = syscon_node_to_regmap(syscon_np);
+       if (IS_ERR(edge->ipc_regmap))
+               return PTR_ERR(edge->ipc_regmap);
+
+       key = "qcom,ipc";
+       ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset);
+       if (ret < 0) {
+               dev_err(dev, "no offset in %s\n", key);
+               return -EINVAL;
+       }
+
+       ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit);
+       if (ret < 0) {
+               dev_err(dev, "no bit in %s\n", key);
+               return -EINVAL;
+       }
+
+       irq = irq_of_parse_and_map(node, 0);
+       if (irq < 0) {
+               dev_err(dev, "required smd interrupt missing\n");
+               return -EINVAL;
+       }
+
+       ret = devm_request_irq(dev, irq,
+                              qcom_smd_edge_intr, IRQF_TRIGGER_RISING,
+                              node->name, edge);
+       if (ret) {
+               dev_err(dev, "failed to request smd irq\n");
+               return ret;
+       }
+
+       edge->irq = irq;
+
+       return 0;
+}
+
+/*
+ * Release function for an edge.
+  * Reset the state of each associated channel and free the edge context.
+ */
+static void qcom_smd_edge_release(struct device *dev)
+{
+       struct qcom_smd_channel *channel;
+       struct qcom_smd_edge *edge = to_smd_edge(dev);
+
+       list_for_each_entry(channel, &edge->channels, list) {
+               SET_RX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED);
+               SET_RX_CHANNEL_INFO(channel, head, 0);
+               SET_RX_CHANNEL_INFO(channel, tail, 0);
+       }
+
+       kfree(edge);
+}
+
+/**
+ * qcom_smd_register_edge() - register an edge based on an device_node
+ * @parent:    parent device for the edge
+ * @node:      device_node describing the edge
+ *
+ * Returns an edge reference, or negative ERR_PTR() on failure.
+ */
+struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent,
+                                            struct device_node *node)
+{
+       struct qcom_smd_edge *edge;
+       int ret;
+
+       edge = kzalloc(sizeof(*edge), GFP_KERNEL);
+       if (!edge)
+               return ERR_PTR(-ENOMEM);
+
+       init_waitqueue_head(&edge->new_channel_event);
+
+       edge->dev.parent = parent;
+       edge->dev.release = qcom_smd_edge_release;
+       dev_set_name(&edge->dev, "%s:%s", dev_name(parent), node->name);
+       ret = device_register(&edge->dev);
+       if (ret) {
+               pr_err("failed to register smd edge\n");
+               return ERR_PTR(ret);
+       }
+
+       ret = qcom_smd_parse_edge(&edge->dev, node, edge);
+       if (ret) {
+               dev_err(&edge->dev, "failed to parse smd edge\n");
+               goto unregister_dev;
+       }
+
+       schedule_work(&edge->scan_work);
+
+       return edge;
+
+unregister_dev:
+       put_device(&edge->dev);
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(qcom_smd_register_edge);
+
+static int qcom_smd_remove_device(struct device *dev, void *data)
+{
+       device_unregister(dev);
+
+       return 0;
+}
+
+/**
+ * qcom_smd_unregister_edge() - release an edge and its children
+ * @edge:      edge reference acquired from qcom_smd_register_edge
+ */
+int qcom_smd_unregister_edge(struct qcom_smd_edge *edge)
+{
+       int ret;
+
+       disable_irq(edge->irq);
+       cancel_work_sync(&edge->scan_work);
+       cancel_work_sync(&edge->state_work);
+
+       ret = device_for_each_child(&edge->dev, NULL, qcom_smd_remove_device);
+       if (ret)
+               dev_warn(&edge->dev, "can't remove smd device: %d\n", ret);
+
+       device_unregister(&edge->dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(qcom_smd_unregister_edge);
+
+static int qcom_smd_probe(struct platform_device *pdev)
+{
+       struct device_node *node;
+       void *p;
+
+       /* Wait for smem */
+       p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL);
+       if (PTR_ERR(p) == -EPROBE_DEFER)
+               return PTR_ERR(p);
+
+       for_each_available_child_of_node(pdev->dev.of_node, node)
+               qcom_smd_register_edge(&pdev->dev, node);
+
+       return 0;
+}
+
+static int qcom_smd_remove_edge(struct device *dev, void *data)
+{
+       struct qcom_smd_edge *edge = to_smd_edge(dev);
+
+       return qcom_smd_unregister_edge(edge);
+}
+
+/*
+ * Shut down all smd clients by making sure that each edge stops processing
+ * events and scanning for new channels, then call destroy on the devices.
+ */
+static int qcom_smd_remove(struct platform_device *pdev)
+{
+       int ret;
+
+       ret = device_for_each_child(&pdev->dev, NULL, qcom_smd_remove_edge);
+       if (ret)
+               dev_warn(&pdev->dev, "can't remove smd device: %d\n", ret);
+
+       return ret;
+}
+
+static const struct of_device_id qcom_smd_of_match[] = {
+       { .compatible = "qcom,smd" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, qcom_smd_of_match);
+
+static struct platform_driver qcom_smd_driver = {
+       .probe = qcom_smd_probe,
+       .remove = qcom_smd_remove,
+       .driver = {
+               .name = "qcom-smd",
+               .of_match_table = qcom_smd_of_match,
+       },
+};
+
+static int __init qcom_smd_init(void)
+{
+       return platform_driver_register(&qcom_smd_driver);
+}
+subsys_initcall(qcom_smd_init);
+
+static void __exit qcom_smd_exit(void)
+{
+       platform_driver_unregister(&qcom_smd_driver);
+}
+module_exit(qcom_smd_exit);
+
+MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
+MODULE_DESCRIPTION("Qualcomm Shared Memory Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c
new file mode 100644 (file)
index 0000000..b6ea9ff
--- /dev/null
@@ -0,0 +1,498 @@
+/*
+ * remote processor messaging bus
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * Ohad Ben-Cohen <ohad@wizery.com>
+ * Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include "rpmsg_internal.h"
+
+/**
+ * rpmsg_create_ept() - create a new rpmsg_endpoint
+ * @rpdev: rpmsg channel device
+ * @cb: rx callback handler
+ * @priv: private data for the driver's use
+ * @chinfo: channel_info with the local rpmsg address to bind with @cb
+ *
+ * Every rpmsg address in the system is bound to an rx callback (so when
+ * inbound messages arrive, they are dispatched by the rpmsg bus using the
+ * appropriate callback handler) by means of an rpmsg_endpoint struct.
+ *
+ * This function allows drivers to create such an endpoint, and by that,
+ * bind a callback, and possibly some private data too, to an rpmsg address
+ * (either one that is known in advance, or one that will be dynamically
+ * assigned for them).
+ *
+ * Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint
+ * is already created for them when they are probed by the rpmsg bus
+ * (using the rx callback provided when they registered to the rpmsg bus).
+ *
+ * So things should just work for simple drivers: they already have an
+ * endpoint, their rx callback is bound to their rpmsg address, and when
+ * relevant inbound messages arrive (i.e. messages which their dst address
+ * equals to the src address of their rpmsg channel), the driver's handler
+ * is invoked to process it.
+ *
+ * That said, more complicated drivers might do need to allocate
+ * additional rpmsg addresses, and bind them to different rx callbacks.
+ * To accomplish that, those drivers need to call this function.
+ *
+ * Drivers should provide their @rpdev channel (so the new endpoint would belong
+ * to the same remote processor their channel belongs to), an rx callback
+ * function, an optional private data (which is provided back when the
+ * rx callback is invoked), and an address they want to bind with the
+ * callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will
+ * dynamically assign them an available rpmsg address (drivers should have
+ * a very good reason why not to always use RPMSG_ADDR_ANY here).
+ *
+ * Returns a pointer to the endpoint on success, or NULL on error.
+ */
+struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *rpdev,
+                                       rpmsg_rx_cb_t cb, void *priv,
+                                       struct rpmsg_channel_info chinfo)
+{
+       return rpdev->ops->create_ept(rpdev, cb, priv, chinfo);
+}
+EXPORT_SYMBOL(rpmsg_create_ept);
+
+/**
+ * rpmsg_destroy_ept() - destroy an existing rpmsg endpoint
+ * @ept: endpoing to destroy
+ *
+ * Should be used by drivers to destroy an rpmsg endpoint previously
+ * created with rpmsg_create_ept().
+ */
+void rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
+{
+       ept->ops->destroy_ept(ept);
+}
+EXPORT_SYMBOL(rpmsg_destroy_ept);
+
+/**
+ * rpmsg_send() - send a message across to the remote processor
+ * @ept: the rpmsg endpoint
+ * @data: payload of message
+ * @len: length of payload
+ *
+ * This function sends @data of length @len on the @ept endpoint.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to, using @ept's address and its associated rpmsg
+ * device destination addresses.
+ * In case there are no TX buffers available, the function will block until
+ * one becomes available, or a timeout of 15 seconds elapses. When the latter
+ * happens, -ERESTARTSYS is returned.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       return ept->ops->send(ept, data, len);
+}
+EXPORT_SYMBOL(rpmsg_send);
+
+/**
+ * rpmsg_sendto() - send a message across to the remote processor, specify dst
+ * @ept: the rpmsg endpoint
+ * @data: payload of message
+ * @len: length of payload
+ * @dst: destination address
+ *
+ * This function sends @data of length @len to the remote @dst address.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to, using @ept's address as source.
+ * In case there are no TX buffers available, the function will block until
+ * one becomes available, or a timeout of 15 seconds elapses. When the latter
+ * happens, -ERESTARTSYS is returned.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
+{
+       return ept->ops->sendto(ept, data, len, dst);
+}
+EXPORT_SYMBOL(rpmsg_sendto);
+
+/**
+ * rpmsg_send_offchannel() - send a message using explicit src/dst addresses
+ * @ept: the rpmsg endpoint
+ * @src: source address
+ * @dst: destination address
+ * @data: payload of message
+ * @len: length of payload
+ *
+ * This function sends @data of length @len to the remote @dst address,
+ * and uses @src as the source address.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to.
+ * In case there are no TX buffers available, the function will block until
+ * one becomes available, or a timeout of 15 seconds elapses. When the latter
+ * happens, -ERESTARTSYS is returned.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                         void *data, int len)
+{
+       return ept->ops->send_offchannel(ept, src, dst, data, len);
+}
+EXPORT_SYMBOL(rpmsg_send_offchannel);
+
+/**
+ * rpmsg_send() - send a message across to the remote processor
+ * @ept: the rpmsg endpoint
+ * @data: payload of message
+ * @len: length of payload
+ *
+ * This function sends @data of length @len on the @ept endpoint.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to, using @ept's address as source and its associated
+ * rpdev's address as destination.
+ * In case there are no TX buffers available, the function will immediately
+ * return -ENOMEM without waiting until one becomes available.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       return ept->ops->trysend(ept, data, len);
+}
+EXPORT_SYMBOL(rpmsg_trysend);
+
+/**
+ * rpmsg_sendto() - send a message across to the remote processor, specify dst
+ * @ept: the rpmsg endpoint
+ * @data: payload of message
+ * @len: length of payload
+ * @dst: destination address
+ *
+ * This function sends @data of length @len to the remote @dst address.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to, using @ept's address as source.
+ * In case there are no TX buffers available, the function will immediately
+ * return -ENOMEM without waiting until one becomes available.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst)
+{
+       return ept->ops->trysendto(ept, data, len, dst);
+}
+EXPORT_SYMBOL(rpmsg_trysendto);
+
+/**
+ * rpmsg_send_offchannel() - send a message using explicit src/dst addresses
+ * @ept: the rpmsg endpoint
+ * @src: source address
+ * @dst: destination address
+ * @data: payload of message
+ * @len: length of payload
+ *
+ * This function sends @data of length @len to the remote @dst address,
+ * and uses @src as the source address.
+ * The message will be sent to the remote processor which the @ept
+ * endpoint belongs to.
+ * In case there are no TX buffers available, the function will immediately
+ * return -ENOMEM without waiting until one becomes available.
+ *
+ * Can only be called from process context (for now).
+ *
+ * Returns 0 on success and an appropriate error value on failure.
+ */
+int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                            void *data, int len)
+{
+       return ept->ops->trysend_offchannel(ept, src, dst, data, len);
+}
+EXPORT_SYMBOL(rpmsg_trysend_offchannel);
+
+/*
+ * match an rpmsg channel with a channel info struct.
+ * this is used to make sure we're not creating rpmsg devices for channels
+ * that already exist.
+ */
+static int rpmsg_device_match(struct device *dev, void *data)
+{
+       struct rpmsg_channel_info *chinfo = data;
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+
+       if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src)
+               return 0;
+
+       if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst)
+               return 0;
+
+       if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE))
+               return 0;
+
+       /* found a match ! */
+       return 1;
+}
+
+struct device *rpmsg_find_device(struct device *parent,
+                                struct rpmsg_channel_info *chinfo)
+{
+       return device_find_child(parent, chinfo, rpmsg_device_match);
+
+}
+EXPORT_SYMBOL(rpmsg_find_device);
+
+/* sysfs show configuration fields */
+#define rpmsg_show_attr(field, path, format_string)                    \
+static ssize_t                                                         \
+field##_show(struct device *dev,                                       \
+                       struct device_attribute *attr, char *buf)       \
+{                                                                      \
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);              \
+                                                                       \
+       return sprintf(buf, format_string, rpdev->path);                \
+}
+
+/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */
+rpmsg_show_attr(name, id.name, "%s\n");
+rpmsg_show_attr(src, src, "0x%x\n");
+rpmsg_show_attr(dst, dst, "0x%x\n");
+rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n");
+
+static ssize_t modalias_show(struct device *dev,
+                            struct device_attribute *attr, char *buf)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+
+       return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name);
+}
+
+static struct device_attribute rpmsg_dev_attrs[] = {
+       __ATTR_RO(name),
+       __ATTR_RO(modalias),
+       __ATTR_RO(dst),
+       __ATTR_RO(src),
+       __ATTR_RO(announce),
+       __ATTR_NULL
+};
+
+/* rpmsg devices and drivers are matched using the service name */
+static inline int rpmsg_id_match(const struct rpmsg_device *rpdev,
+                                 const struct rpmsg_device_id *id)
+{
+       return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0;
+}
+
+/* match rpmsg channel and rpmsg driver */
+static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+       struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv);
+       const struct rpmsg_device_id *ids = rpdrv->id_table;
+       unsigned int i;
+
+       if (ids)
+               for (i = 0; ids[i].name[0]; i++)
+                       if (rpmsg_id_match(rpdev, &ids[i]))
+                               return 1;
+
+       return of_driver_match_device(dev, drv);
+}
+
+static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+
+       return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT,
+                                       rpdev->id.name);
+}
+
+/*
+ * when an rpmsg driver is probed with a channel, we seamlessly create
+ * it an endpoint, binding its rx callback to a unique local rpmsg
+ * address.
+ *
+ * if we need to, we also announce about this channel to the remote
+ * processor (needed in case the driver is exposing an rpmsg service).
+ */
+static int rpmsg_dev_probe(struct device *dev)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+       struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
+       struct rpmsg_channel_info chinfo = {};
+       struct rpmsg_endpoint *ept;
+       int err;
+
+       strncpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
+       chinfo.src = rpdev->src;
+       chinfo.dst = RPMSG_ADDR_ANY;
+
+       ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, chinfo);
+       if (!ept) {
+               dev_err(dev, "failed to create endpoint\n");
+               err = -ENOMEM;
+               goto out;
+       }
+
+       rpdev->ept = ept;
+       rpdev->src = ept->addr;
+
+       err = rpdrv->probe(rpdev);
+       if (err) {
+               dev_err(dev, "%s: failed: %d\n", __func__, err);
+               rpmsg_destroy_ept(ept);
+               goto out;
+       }
+
+       if (rpdev->ops->announce_create)
+               err = rpdev->ops->announce_create(rpdev);
+out:
+       return err;
+}
+
+static int rpmsg_dev_remove(struct device *dev)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+       struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
+       int err = 0;
+
+       if (rpdev->ops->announce_destroy)
+               err = rpdev->ops->announce_destroy(rpdev);
+
+       rpdrv->remove(rpdev);
+
+       rpmsg_destroy_ept(rpdev->ept);
+
+       return err;
+}
+
+static struct bus_type rpmsg_bus = {
+       .name           = "rpmsg",
+       .match          = rpmsg_dev_match,
+       .dev_attrs      = rpmsg_dev_attrs,
+       .uevent         = rpmsg_uevent,
+       .probe          = rpmsg_dev_probe,
+       .remove         = rpmsg_dev_remove,
+};
+
+static void rpmsg_release_device(struct device *dev)
+{
+       struct rpmsg_device *rpdev = to_rpmsg_device(dev);
+
+       kfree(rpdev);
+}
+
+int rpmsg_register_device(struct rpmsg_device *rpdev)
+{
+       struct device *dev = &rpdev->dev;
+       int ret;
+
+       dev_set_name(&rpdev->dev, "%s:%s",
+                    dev_name(dev->parent), rpdev->id.name);
+
+       rpdev->dev.bus = &rpmsg_bus;
+       rpdev->dev.release = rpmsg_release_device;
+
+       ret = device_register(&rpdev->dev);
+       if (ret) {
+               dev_err(dev, "device_register failed: %d\n", ret);
+               put_device(&rpdev->dev);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(rpmsg_register_device);
+
+/*
+ * find an existing channel using its name + address properties,
+ * and destroy it
+ */
+int rpmsg_unregister_device(struct device *parent,
+                           struct rpmsg_channel_info *chinfo)
+{
+       struct device *dev;
+
+       dev = rpmsg_find_device(parent, chinfo);
+       if (!dev)
+               return -EINVAL;
+
+       device_unregister(dev);
+
+       put_device(dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(rpmsg_unregister_device);
+
+/**
+ * __register_rpmsg_driver() - register an rpmsg driver with the rpmsg bus
+ * @rpdrv: pointer to a struct rpmsg_driver
+ * @owner: owning module/driver
+ *
+ * Returns 0 on success, and an appropriate error value on failure.
+ */
+int __register_rpmsg_driver(struct rpmsg_driver *rpdrv, struct module *owner)
+{
+       rpdrv->drv.bus = &rpmsg_bus;
+       rpdrv->drv.owner = owner;
+       return driver_register(&rpdrv->drv);
+}
+EXPORT_SYMBOL(__register_rpmsg_driver);
+
+/**
+ * unregister_rpmsg_driver() - unregister an rpmsg driver from the rpmsg bus
+ * @rpdrv: pointer to a struct rpmsg_driver
+ *
+ * Returns 0 on success, and an appropriate error value on failure.
+ */
+void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv)
+{
+       driver_unregister(&rpdrv->drv);
+}
+EXPORT_SYMBOL(unregister_rpmsg_driver);
+
+
+static int __init rpmsg_init(void)
+{
+       int ret;
+
+       ret = bus_register(&rpmsg_bus);
+       if (ret)
+               pr_err("failed to register rpmsg bus: %d\n", ret);
+
+       return ret;
+}
+postcore_initcall(rpmsg_init);
+
+static void __exit rpmsg_fini(void)
+{
+       bus_unregister(&rpmsg_bus);
+}
+module_exit(rpmsg_fini);
+
+MODULE_DESCRIPTION("remote processor messaging bus");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h
new file mode 100644 (file)
index 0000000..8075a20
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * remote processor messaging bus internals
+ *
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * Ohad Ben-Cohen <ohad@wizery.com>
+ * Brian Swetland <swetland@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __RPMSG_INTERNAL_H__
+#define __RPMSG_INTERNAL_H__
+
+#include <linux/rpmsg.h>
+
+#define to_rpmsg_device(d) container_of(d, struct rpmsg_device, dev)
+#define to_rpmsg_driver(d) container_of(d, struct rpmsg_driver, drv)
+
+/**
+ * struct rpmsg_device_ops - indirection table for the rpmsg_device operations
+ * @create_ept:                create backend-specific endpoint, requried
+ * @announce_create:   announce presence of new channel, optional
+ * @announce_destroy:  announce destruction of channel, optional
+ *
+ * Indirection table for the operations that a rpmsg backend should implement.
+ * @announce_create and @announce_destroy are optional as the backend might
+ * advertise new channels implicitly by creating the endpoints.
+ */
+struct rpmsg_device_ops {
+       struct rpmsg_endpoint *(*create_ept)(struct rpmsg_device *rpdev,
+                                           rpmsg_rx_cb_t cb, void *priv,
+                                           struct rpmsg_channel_info chinfo);
+
+       int (*announce_create)(struct rpmsg_device *ept);
+       int (*announce_destroy)(struct rpmsg_device *ept);
+};
+
+/**
+ * struct rpmsg_endpoint_ops - indirection table for rpmsg_endpoint operations
+ * @destroy_ept:       destroy the given endpoint, required
+ * @send:              see @rpmsg_send(), required
+ * @sendto:            see @rpmsg_sendto(), optional
+ * @send_offchannel:   see @rpmsg_send_offchannel(), optional
+ * @trysend:           see @rpmsg_trysend(), required
+ * @trysendto:         see @rpmsg_trysendto(), optional
+ * @trysend_offchannel:        see @rpmsg_trysend_offchannel(), optional
+ *
+ * Indirection table for the operations that a rpmsg backend should implement.
+ * In addition to @destroy_ept, the backend must at least implement @send and
+ * @trysend, while the variants sending data off-channel are optional.
+ */
+struct rpmsg_endpoint_ops {
+       void (*destroy_ept)(struct rpmsg_endpoint *ept);
+
+       int (*send)(struct rpmsg_endpoint *ept, void *data, int len);
+       int (*sendto)(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
+       int (*send_offchannel)(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                                 void *data, int len);
+
+       int (*trysend)(struct rpmsg_endpoint *ept, void *data, int len);
+       int (*trysendto)(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
+       int (*trysend_offchannel)(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                            void *data, int len);
+};
+
+int rpmsg_register_device(struct rpmsg_device *rpdev);
+int rpmsg_unregister_device(struct device *parent,
+                           struct rpmsg_channel_info *chinfo);
+
+struct device *rpmsg_find_device(struct device *parent,
+                                struct rpmsg_channel_info *chinfo);
+
+#endif
index fe03b2aef4509d26b2eeaf8f67a932957ba963bf..3090b0d3072f1ed8964b1697562d704abf7a99cf 100644 (file)
@@ -33,6 +33,9 @@
 #include <linux/wait.h>
 #include <linux/rpmsg.h>
 #include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include "rpmsg_internal.h"
 
 /**
  * struct virtproc_info - virtual remote processor state
@@ -72,20 +75,69 @@ struct virtproc_info {
        struct rpmsg_endpoint *ns_ept;
 };
 
+/* The feature bitmap for virtio rpmsg */
+#define VIRTIO_RPMSG_F_NS      0 /* RP supports name service notifications */
+
 /**
- * struct rpmsg_channel_info - internal channel info representation
- * @name: name of service
- * @src: local address
+ * struct rpmsg_hdr - common header for all rpmsg messages
+ * @src: source address
  * @dst: destination address
+ * @reserved: reserved for future use
+ * @len: length of payload (in bytes)
+ * @flags: message flags
+ * @data: @len bytes of message payload data
+ *
+ * Every message sent(/received) on the rpmsg bus begins with this header.
  */
-struct rpmsg_channel_info {
-       char name[RPMSG_NAME_SIZE];
+struct rpmsg_hdr {
        u32 src;
        u32 dst;
+       u32 reserved;
+       u16 len;
+       u16 flags;
+       u8 data[0];
+} __packed;
+
+/**
+ * struct rpmsg_ns_msg - dynamic name service announcement message
+ * @name: name of remote service that is published
+ * @addr: address of remote service that is published
+ * @flags: indicates whether service is created or destroyed
+ *
+ * This message is sent across to publish a new service, or announce
+ * about its removal. When we receive these messages, an appropriate
+ * rpmsg channel (i.e device) is created/destroyed. In turn, the ->probe()
+ * or ->remove() handler of the appropriate rpmsg driver will be invoked
+ * (if/as-soon-as one is registered).
+ */
+struct rpmsg_ns_msg {
+       char name[RPMSG_NAME_SIZE];
+       u32 addr;
+       u32 flags;
+} __packed;
+
+/**
+ * enum rpmsg_ns_flags - dynamic name service announcement flags
+ *
+ * @RPMSG_NS_CREATE: a new remote service was just created
+ * @RPMSG_NS_DESTROY: a known remote service was just destroyed
+ */
+enum rpmsg_ns_flags {
+       RPMSG_NS_CREATE         = 0,
+       RPMSG_NS_DESTROY        = 1,
+};
+
+/**
+ * @vrp: the remote processor this channel belongs to
+ */
+struct virtio_rpmsg_channel {
+       struct rpmsg_device rpdev;
+
+       struct virtproc_info *vrp;
 };
 
-#define to_rpmsg_channel(d) container_of(d, struct rpmsg_channel, dev)
-#define to_rpmsg_driver(d) container_of(d, struct rpmsg_driver, drv)
+#define to_virtio_rpmsg_channel(_rpdev) \
+       container_of(_rpdev, struct virtio_rpmsg_channel, rpdev)
 
 /*
  * We're allocating buffers of 512 bytes each for communications. The
@@ -118,78 +170,28 @@ struct rpmsg_channel_info {
 /* Address 53 is reserved for advertising remote services */
 #define RPMSG_NS_ADDR                  (53)
 
-/* sysfs show configuration fields */
-#define rpmsg_show_attr(field, path, format_string)                    \
-static ssize_t                                                         \
-field##_show(struct device *dev,                                       \
-                       struct device_attribute *attr, char *buf)       \
-{                                                                      \
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);            \
-                                                                       \
-       return sprintf(buf, format_string, rpdev->path);                \
-}
-
-/* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */
-rpmsg_show_attr(name, id.name, "%s\n");
-rpmsg_show_attr(src, src, "0x%x\n");
-rpmsg_show_attr(dst, dst, "0x%x\n");
-rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n");
-
-/*
- * Unique (and free running) index for rpmsg devices.
- *
- * Yeah, we're not recycling those numbers (yet?). will be easy
- * to change if/when we want to.
- */
-static unsigned int rpmsg_dev_index;
-
-static ssize_t modalias_show(struct device *dev,
-                            struct device_attribute *attr, char *buf)
-{
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-
-       return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name);
-}
-
-static struct device_attribute rpmsg_dev_attrs[] = {
-       __ATTR_RO(name),
-       __ATTR_RO(modalias),
-       __ATTR_RO(dst),
-       __ATTR_RO(src),
-       __ATTR_RO(announce),
-       __ATTR_NULL
+static void virtio_rpmsg_destroy_ept(struct rpmsg_endpoint *ept);
+static int virtio_rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len);
+static int virtio_rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len,
+                              u32 dst);
+static int virtio_rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src,
+                                       u32 dst, void *data, int len);
+static int virtio_rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len);
+static int virtio_rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data,
+                                 int len, u32 dst);
+static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
+                                          u32 dst, void *data, int len);
+
+static const struct rpmsg_endpoint_ops virtio_endpoint_ops = {
+       .destroy_ept = virtio_rpmsg_destroy_ept,
+       .send = virtio_rpmsg_send,
+       .sendto = virtio_rpmsg_sendto,
+       .send_offchannel = virtio_rpmsg_send_offchannel,
+       .trysend = virtio_rpmsg_trysend,
+       .trysendto = virtio_rpmsg_trysendto,
+       .trysend_offchannel = virtio_rpmsg_trysend_offchannel,
 };
 
-/* rpmsg devices and drivers are matched using the service name */
-static inline int rpmsg_id_match(const struct rpmsg_channel *rpdev,
-                                 const struct rpmsg_device_id *id)
-{
-       return strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE) == 0;
-}
-
-/* match rpmsg channel and rpmsg driver */
-static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
-{
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-       struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv);
-       const struct rpmsg_device_id *ids = rpdrv->id_table;
-       unsigned int i;
-
-       for (i = 0; ids[i].name[0]; i++)
-               if (rpmsg_id_match(rpdev, &ids[i]))
-                       return 1;
-
-       return 0;
-}
-
-static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env)
-{
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-
-       return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT,
-                                       rpdev->id.name);
-}
-
 /**
  * __ept_release() - deallocate an rpmsg endpoint
  * @kref: the ept's reference count
@@ -212,18 +214,17 @@ static void __ept_release(struct kref *kref)
 
 /* for more info, see below documentation of rpmsg_create_ept() */
 static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,
-               struct rpmsg_channel *rpdev, rpmsg_rx_cb_t cb,
-               void *priv, u32 addr)
+                                                struct rpmsg_device *rpdev,
+                                                rpmsg_rx_cb_t cb,
+                                                void *priv, u32 addr)
 {
        int id_min, id_max, id;
        struct rpmsg_endpoint *ept;
        struct device *dev = rpdev ? &rpdev->dev : &vrp->vdev->dev;
 
        ept = kzalloc(sizeof(*ept), GFP_KERNEL);
-       if (!ept) {
-               dev_err(dev, "failed to kzalloc a new ept\n");
+       if (!ept)
                return NULL;
-       }
 
        kref_init(&ept->refcount);
        mutex_init(&ept->cb_lock);
@@ -231,6 +232,7 @@ static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp,
        ept->rpdev = rpdev;
        ept->cb = cb;
        ept->priv = priv;
+       ept->ops = &virtio_endpoint_ops;
 
        /* do we need to allocate a local address ? */
        if (addr == RPMSG_ADDR_ANY) {
@@ -261,52 +263,15 @@ free_ept:
        return NULL;
 }
 
-/**
- * rpmsg_create_ept() - create a new rpmsg_endpoint
- * @rpdev: rpmsg channel device
- * @cb: rx callback handler
- * @priv: private data for the driver's use
- * @addr: local rpmsg address to bind with @cb
- *
- * Every rpmsg address in the system is bound to an rx callback (so when
- * inbound messages arrive, they are dispatched by the rpmsg bus using the
- * appropriate callback handler) by means of an rpmsg_endpoint struct.
- *
- * This function allows drivers to create such an endpoint, and by that,
- * bind a callback, and possibly some private data too, to an rpmsg address
- * (either one that is known in advance, or one that will be dynamically
- * assigned for them).
- *
- * Simple rpmsg drivers need not call rpmsg_create_ept, because an endpoint
- * is already created for them when they are probed by the rpmsg bus
- * (using the rx callback provided when they registered to the rpmsg bus).
- *
- * So things should just work for simple drivers: they already have an
- * endpoint, their rx callback is bound to their rpmsg address, and when
- * relevant inbound messages arrive (i.e. messages which their dst address
- * equals to the src address of their rpmsg channel), the driver's handler
- * is invoked to process it.
- *
- * That said, more complicated drivers might do need to allocate
- * additional rpmsg addresses, and bind them to different rx callbacks.
- * To accomplish that, those drivers need to call this function.
- *
- * Drivers should provide their @rpdev channel (so the new endpoint would belong
- * to the same remote processor their channel belongs to), an rx callback
- * function, an optional private data (which is provided back when the
- * rx callback is invoked), and an address they want to bind with the
- * callback. If @addr is RPMSG_ADDR_ANY, then rpmsg_create_ept will
- * dynamically assign them an available rpmsg address (drivers should have
- * a very good reason why not to always use RPMSG_ADDR_ANY here).
- *
- * Returns a pointer to the endpoint on success, or NULL on error.
- */
-struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_channel *rpdev,
-                               rpmsg_rx_cb_t cb, void *priv, u32 addr)
+static struct rpmsg_endpoint *virtio_rpmsg_create_ept(struct rpmsg_device *rpdev,
+                                                     rpmsg_rx_cb_t cb,
+                                                     void *priv,
+                                                     struct rpmsg_channel_info chinfo)
 {
-       return __rpmsg_create_ept(rpdev->vrp, rpdev, cb, priv, addr);
+       struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+
+       return __rpmsg_create_ept(vch->vrp, rpdev, cb, priv, chinfo.src);
 }
-EXPORT_SYMBOL(rpmsg_create_ept);
 
 /**
  * __rpmsg_destroy_ept() - destroy an existing rpmsg endpoint
@@ -334,178 +299,82 @@ __rpmsg_destroy_ept(struct virtproc_info *vrp, struct rpmsg_endpoint *ept)
        kref_put(&ept->refcount, __ept_release);
 }
 
-/**
- * rpmsg_destroy_ept() - destroy an existing rpmsg endpoint
- * @ept: endpoing to destroy
- *
- * Should be used by drivers to destroy an rpmsg endpoint previously
- * created with rpmsg_create_ept().
- */
-void rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
+static void virtio_rpmsg_destroy_ept(struct rpmsg_endpoint *ept)
 {
-       __rpmsg_destroy_ept(ept->rpdev->vrp, ept);
+       struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(ept->rpdev);
+
+       __rpmsg_destroy_ept(vch->vrp, ept);
 }
-EXPORT_SYMBOL(rpmsg_destroy_ept);
 
-/*
- * when an rpmsg driver is probed with a channel, we seamlessly create
- * it an endpoint, binding its rx callback to a unique local rpmsg
- * address.
- *
- * if we need to, we also announce about this channel to the remote
- * processor (needed in case the driver is exposing an rpmsg service).
- */
-static int rpmsg_dev_probe(struct device *dev)
+static int virtio_rpmsg_announce_create(struct rpmsg_device *rpdev)
 {
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-       struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
-       struct virtproc_info *vrp = rpdev->vrp;
-       struct rpmsg_endpoint *ept;
-       int err;
-
-       ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, rpdev->src);
-       if (!ept) {
-               dev_err(dev, "failed to create endpoint\n");
-               err = -ENOMEM;
-               goto out;
-       }
-
-       rpdev->ept = ept;
-       rpdev->src = ept->addr;
-
-       err = rpdrv->probe(rpdev);
-       if (err) {
-               dev_err(dev, "%s: failed: %d\n", __func__, err);
-               rpmsg_destroy_ept(ept);
-               goto out;
-       }
+       struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+       struct virtproc_info *vrp = vch->vrp;
+       struct device *dev = &rpdev->dev;
+       int err = 0;
 
        /* need to tell remote processor's name service about this channel ? */
        if (rpdev->announce &&
-                       virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) {
+           virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) {
                struct rpmsg_ns_msg nsm;
 
                strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE);
-               nsm.addr = rpdev->src;
+               nsm.addr = rpdev->ept->addr;
                nsm.flags = RPMSG_NS_CREATE;
 
-               err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR);
+               err = rpmsg_sendto(rpdev->ept, &nsm, sizeof(nsm), RPMSG_NS_ADDR);
                if (err)
                        dev_err(dev, "failed to announce service %d\n", err);
        }
 
-out:
        return err;
 }
 
-static int rpmsg_dev_remove(struct device *dev)
+static int virtio_rpmsg_announce_destroy(struct rpmsg_device *rpdev)
 {
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-       struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver);
-       struct virtproc_info *vrp = rpdev->vrp;
+       struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+       struct virtproc_info *vrp = vch->vrp;
+       struct device *dev = &rpdev->dev;
        int err = 0;
 
        /* tell remote processor's name service we're removing this channel */
        if (rpdev->announce &&
-                       virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) {
+           virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) {
                struct rpmsg_ns_msg nsm;
 
                strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE);
                nsm.addr = rpdev->src;
                nsm.flags = RPMSG_NS_DESTROY;
 
-               err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR);
+               err = rpmsg_sendto(rpdev->ept, &nsm, sizeof(nsm), RPMSG_NS_ADDR);
                if (err)
                        dev_err(dev, "failed to announce service %d\n", err);
        }
 
-       rpdrv->remove(rpdev);
-
-       rpmsg_destroy_ept(rpdev->ept);
-
        return err;
 }
 
-static struct bus_type rpmsg_bus = {
-       .name           = "rpmsg",
-       .match          = rpmsg_dev_match,
-       .dev_attrs      = rpmsg_dev_attrs,
-       .uevent         = rpmsg_uevent,
-       .probe          = rpmsg_dev_probe,
-       .remove         = rpmsg_dev_remove,
+static const struct rpmsg_device_ops virtio_rpmsg_ops = {
+       .create_ept = virtio_rpmsg_create_ept,
+       .announce_create = virtio_rpmsg_announce_create,
+       .announce_destroy = virtio_rpmsg_announce_destroy,
 };
 
-/**
- * __register_rpmsg_driver() - register an rpmsg driver with the rpmsg bus
- * @rpdrv: pointer to a struct rpmsg_driver
- * @owner: owning module/driver
- *
- * Returns 0 on success, and an appropriate error value on failure.
- */
-int __register_rpmsg_driver(struct rpmsg_driver *rpdrv, struct module *owner)
-{
-       rpdrv->drv.bus = &rpmsg_bus;
-       rpdrv->drv.owner = owner;
-       return driver_register(&rpdrv->drv);
-}
-EXPORT_SYMBOL(__register_rpmsg_driver);
-
-/**
- * unregister_rpmsg_driver() - unregister an rpmsg driver from the rpmsg bus
- * @rpdrv: pointer to a struct rpmsg_driver
- *
- * Returns 0 on success, and an appropriate error value on failure.
- */
-void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv)
-{
-       driver_unregister(&rpdrv->drv);
-}
-EXPORT_SYMBOL(unregister_rpmsg_driver);
-
-static void rpmsg_release_device(struct device *dev)
-{
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-
-       kfree(rpdev);
-}
-
-/*
- * match an rpmsg channel with a channel info struct.
- * this is used to make sure we're not creating rpmsg devices for channels
- * that already exist.
- */
-static int rpmsg_channel_match(struct device *dev, void *data)
-{
-       struct rpmsg_channel_info *chinfo = data;
-       struct rpmsg_channel *rpdev = to_rpmsg_channel(dev);
-
-       if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src)
-               return 0;
-
-       if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst)
-               return 0;
-
-       if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE))
-               return 0;
-
-       /* found a match ! */
-       return 1;
-}
-
 /*
  * create an rpmsg channel using its name and address info.
  * this function will be used to create both static and dynamic
  * channels.
  */
-static struct rpmsg_channel *rpmsg_create_channel(struct virtproc_info *vrp,
-                               struct rpmsg_channel_info *chinfo)
+static struct rpmsg_device *rpmsg_create_channel(struct virtproc_info *vrp,
+                                                struct rpmsg_channel_info *chinfo)
 {
-       struct rpmsg_channel *rpdev;
+       struct virtio_rpmsg_channel *vch;
+       struct rpmsg_device *rpdev;
        struct device *tmp, *dev = &vrp->vdev->dev;
        int ret;
 
        /* make sure a similar channel doesn't already exist */
-       tmp = device_find_child(dev, chinfo, rpmsg_channel_match);
+       tmp = rpmsg_find_device(dev, chinfo);
        if (tmp) {
                /* decrement the matched device's refcount back */
                put_device(tmp);
@@ -514,62 +383,38 @@ static struct rpmsg_channel *rpmsg_create_channel(struct virtproc_info *vrp,
                return NULL;
        }
 
-       rpdev = kzalloc(sizeof(struct rpmsg_channel), GFP_KERNEL);
-       if (!rpdev) {
-               pr_err("kzalloc failed\n");
+       vch = kzalloc(sizeof(*vch), GFP_KERNEL);
+       if (!vch)
                return NULL;
-       }
 
-       rpdev->vrp = vrp;
+       /* Link the channel to our vrp */
+       vch->vrp = vrp;
+
+       /* Assign callbacks for rpmsg_channel */
+       vch->rpdev.ops = &virtio_rpmsg_ops;
+
+       /* Assign public information to the rpmsg_device */
+       rpdev = &vch->rpdev;
        rpdev->src = chinfo->src;
        rpdev->dst = chinfo->dst;
+       rpdev->ops = &virtio_rpmsg_ops;
 
        /*
         * rpmsg server channels has predefined local address (for now),
         * and their existence needs to be announced remotely
         */
-       rpdev->announce = rpdev->src != RPMSG_ADDR_ANY ? true : false;
+       rpdev->announce = rpdev->src != RPMSG_ADDR_ANY;
 
        strncpy(rpdev->id.name, chinfo->name, RPMSG_NAME_SIZE);
 
-       /* very simple device indexing plumbing which is enough for now */
-       dev_set_name(&rpdev->dev, "rpmsg%d", rpmsg_dev_index++);
-
        rpdev->dev.parent = &vrp->vdev->dev;
-       rpdev->dev.bus = &rpmsg_bus;
-       rpdev->dev.release = rpmsg_release_device;
-
-       ret = device_register(&rpdev->dev);
-       if (ret) {
-               dev_err(dev, "device_register failed: %d\n", ret);
-               put_device(&rpdev->dev);
+       ret = rpmsg_register_device(rpdev);
+       if (ret)
                return NULL;
-       }
 
        return rpdev;
 }
 
-/*
- * find an existing channel using its name + address properties,
- * and destroy it
- */
-static int rpmsg_destroy_channel(struct virtproc_info *vrp,
-                                       struct rpmsg_channel_info *chinfo)
-{
-       struct virtio_device *vdev = vrp->vdev;
-       struct device *dev;
-
-       dev = device_find_child(&vdev->dev, chinfo, rpmsg_channel_match);
-       if (!dev)
-               return -EINVAL;
-
-       device_unregister(dev);
-
-       put_device(dev);
-
-       return 0;
-}
-
 /* super simple buffer "allocator" that is just enough for now */
 static void *get_a_tx_buf(struct virtproc_info *vrp)
 {
@@ -684,10 +529,12 @@ static void rpmsg_downref_sleepers(struct virtproc_info *vrp)
  *
  * Returns 0 on success and an appropriate error value on failure.
  */
-int rpmsg_send_offchannel_raw(struct rpmsg_channel *rpdev, u32 src, u32 dst,
-                                       void *data, int len, bool wait)
+static int rpmsg_send_offchannel_raw(struct rpmsg_device *rpdev,
+                                    u32 src, u32 dst,
+                                    void *data, int len, bool wait)
 {
-       struct virtproc_info *vrp = rpdev->vrp;
+       struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
+       struct virtproc_info *vrp = vch->vrp;
        struct device *dev = &rpdev->dev;
        struct scatterlist sg;
        struct rpmsg_hdr *msg;
@@ -751,10 +598,11 @@ int rpmsg_send_offchannel_raw(struct rpmsg_channel *rpdev, u32 src, u32 dst,
        memcpy(msg->data, data, len);
 
        dev_dbg(dev, "TX From 0x%x, To 0x%x, Len %d, Flags %d, Reserved %d\n",
-                                       msg->src, msg->dst, msg->len,
-                                       msg->flags, msg->reserved);
-       print_hex_dump(KERN_DEBUG, "rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1,
-                                       msg, sizeof(*msg) + msg->len, true);
+               msg->src, msg->dst, msg->len, msg->flags, msg->reserved);
+#if defined(CONFIG_DYNAMIC_DEBUG)
+       dynamic_hex_dump("rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1,
+                        msg, sizeof(*msg) + msg->len, true);
+#endif
 
        sg_init_one(&sg, msg, sizeof(*msg) + len);
 
@@ -780,6 +628,56 @@ out:
 }
 EXPORT_SYMBOL(rpmsg_send_offchannel_raw);
 
+static int virtio_rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+       u32 src = ept->addr, dst = rpdev->dst;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
+}
+
+static int virtio_rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len,
+                              u32 dst)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+       u32 src = ept->addr;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
+}
+
+static int virtio_rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src,
+                                       u32 dst, void *data, int len)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
+}
+
+static int virtio_rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+       u32 src = ept->addr, dst = rpdev->dst;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
+}
+
+static int virtio_rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data,
+                                 int len, u32 dst)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+       u32 src = ept->addr;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
+}
+
+static int virtio_rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src,
+                                          u32 dst, void *data, int len)
+{
+       struct rpmsg_device *rpdev = ept->rpdev;
+
+       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
+}
+
 static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
                             struct rpmsg_hdr *msg, unsigned int len)
 {
@@ -788,17 +686,18 @@ static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
        int err;
 
        dev_dbg(dev, "From: 0x%x, To: 0x%x, Len: %d, Flags: %d, Reserved: %d\n",
-                                       msg->src, msg->dst, msg->len,
-                                       msg->flags, msg->reserved);
-       print_hex_dump(KERN_DEBUG, "rpmsg_virtio RX: ", DUMP_PREFIX_NONE, 16, 1,
-                                       msg, sizeof(*msg) + msg->len, true);
+               msg->src, msg->dst, msg->len, msg->flags, msg->reserved);
+#if defined(CONFIG_DYNAMIC_DEBUG)
+       dynamic_hex_dump("rpmsg_virtio RX: ", DUMP_PREFIX_NONE, 16, 1,
+                        msg, sizeof(*msg) + msg->len, true);
+#endif
 
        /*
         * We currently use fixed-sized buffers, so trivially sanitize
         * the reported payload length.
         */
        if (len > RPMSG_BUF_SIZE ||
-               msg->len > (len - sizeof(struct rpmsg_hdr))) {
+           msg->len > (len - sizeof(struct rpmsg_hdr))) {
                dev_warn(dev, "inbound msg too big: (%d, %d)\n", len, msg->len);
                return -EINVAL;
        }
@@ -865,7 +764,7 @@ static void rpmsg_recv_done(struct virtqueue *rvq)
                msgs_received++;
 
                msg = virtqueue_get_buf(rvq, &len);
-       };
+       }
 
        dev_dbg(dev, "Received %u messages\n", msgs_received);
 
@@ -892,23 +791,24 @@ static void rpmsg_xmit_done(struct virtqueue *svq)
 }
 
 /* invoked when a name service announcement arrives */
-static void rpmsg_ns_cb(struct rpmsg_channel *rpdev, void *data, int len,
-                                                       void *priv, u32 src)
+static int rpmsg_ns_cb(struct rpmsg_device *rpdev, void *data, int len,
+                      void *priv, u32 src)
 {
        struct rpmsg_ns_msg *msg = data;
-       struct rpmsg_channel *newch;
+       struct rpmsg_device *newch;
        struct rpmsg_channel_info chinfo;
        struct virtproc_info *vrp = priv;
        struct device *dev = &vrp->vdev->dev;
        int ret;
 
-       print_hex_dump(KERN_DEBUG, "NS announcement: ",
-                       DUMP_PREFIX_NONE, 16, 1,
-                       data, len, true);
+#if defined(CONFIG_DYNAMIC_DEBUG)
+       dynamic_hex_dump("NS announcement: ", DUMP_PREFIX_NONE, 16, 1,
+                        data, len, true);
+#endif
 
        if (len != sizeof(*msg)) {
                dev_err(dev, "malformed ns msg (%d)\n", len);
-               return;
+               return -EINVAL;
        }
 
        /*
@@ -919,22 +819,22 @@ static void rpmsg_ns_cb(struct rpmsg_channel *rpdev, void *data, int len,
         */
        if (rpdev) {
                dev_err(dev, "anomaly: ns ept has an rpdev handle\n");
-               return;
+               return -EINVAL;
        }
 
        /* don't trust the remote processor for null terminating the name */
        msg->name[RPMSG_NAME_SIZE - 1] = '\0';
 
        dev_info(dev, "%sing channel %s addr 0x%x\n",
-                       msg->flags & RPMSG_NS_DESTROY ? "destroy" : "creat",
-                       msg->name, msg->addr);
+                msg->flags & RPMSG_NS_DESTROY ? "destroy" : "creat",
+                msg->name, msg->addr);
 
        strncpy(chinfo.name, msg->name, sizeof(chinfo.name));
        chinfo.src = RPMSG_ADDR_ANY;
        chinfo.dst = msg->addr;
 
        if (msg->flags & RPMSG_NS_DESTROY) {
-               ret = rpmsg_destroy_channel(vrp, &chinfo);
+               ret = rpmsg_unregister_device(&vrp->vdev->dev, &chinfo);
                if (ret)
                        dev_err(dev, "rpmsg_destroy_channel failed: %d\n", ret);
        } else {
@@ -942,6 +842,8 @@ static void rpmsg_ns_cb(struct rpmsg_channel *rpdev, void *data, int len,
                if (!newch)
                        dev_err(dev, "rpmsg_create_channel failed\n");
        }
+
+       return 0;
 }
 
 static int rpmsg_probe(struct virtio_device *vdev)
@@ -995,8 +897,8 @@ static int rpmsg_probe(struct virtio_device *vdev)
                goto vqs_del;
        }
 
-       dev_dbg(&vdev->dev, "buffers: va %p, dma 0x%llx\n", bufs_va,
-                                       (unsigned long long)vrp->bufs_dma);
+       dev_dbg(&vdev->dev, "buffers: va %p, dma %pad\n",
+               bufs_va, &vrp->bufs_dma);
 
        /* half of the buffers is dedicated for RX */
        vrp->rbufs = bufs_va;
@@ -1012,7 +914,7 @@ static int rpmsg_probe(struct virtio_device *vdev)
                sg_init_one(&sg, cpu_addr, RPMSG_BUF_SIZE);
 
                err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, cpu_addr,
-                                                               GFP_KERNEL);
+                                         GFP_KERNEL);
                WARN_ON(err); /* sanity check; this can't really happen */
        }
 
@@ -1119,17 +1021,9 @@ static int __init rpmsg_init(void)
 {
        int ret;
 
-       ret = bus_register(&rpmsg_bus);
-       if (ret) {
-               pr_err("failed to register rpmsg bus: %d\n", ret);
-               return ret;
-       }
-
        ret = register_virtio_driver(&virtio_ipc_driver);
-       if (ret) {
+       if (ret)
                pr_err("failed to register virtio driver: %d\n", ret);
-               bus_unregister(&rpmsg_bus);
-       }
 
        return ret;
 }
@@ -1138,7 +1032,6 @@ subsys_initcall(rpmsg_init);
 static void __exit rpmsg_fini(void)
 {
        unregister_virtio_driver(&virtio_ipc_driver);
-       bus_unregister(&rpmsg_bus);
 }
 module_exit(rpmsg_fini);
 
index bfbd12b41162bcca18a67be96e070629bbbe841e..71a1b2399c4805c8539a0da0bf592eec20a79302 100644 (file)
@@ -39,9 +39,9 @@ struct omap_rproc_pdata {
        const char *firmware;
        const char *mbox_name;
        const struct rproc_ops *ops;
-       int (*device_enable) (struct platform_device *pdev);
-       int (*device_shutdown) (struct platform_device *pdev);
-       void(*set_bootaddr)(u32);
+       int (*device_enable)(struct platform_device *pdev);
+       int (*device_shutdown)(struct platform_device *pdev);
+       void (*set_bootaddr)(u32);
 };
 
 #if defined(CONFIG_OMAP_REMOTEPROC) || defined(CONFIG_OMAP_REMOTEPROC_MODULE)
index 1c457a8dd5a6a044fa467ad8a38787b373f95d32..c321eab5054e0c1b336b76aab4faf7053fd4f3d0 100644 (file)
@@ -118,7 +118,7 @@ enum fw_resource_type {
        RSC_LAST        = 4,
 };
 
-#define FW_RSC_ADDR_ANY (0xFFFFFFFFFFFFFFFF)
+#define FW_RSC_ADDR_ANY (-1)
 
 /**
  * struct fw_rsc_carveout - physically contiguous memory request
@@ -241,7 +241,7 @@ struct fw_rsc_trace {
  * @notifyid is a unique rproc-wide notify index for this vring. This notify
  * index is used when kicking a remote processor, to let it know that this
  * vring is triggered.
- * @reserved: reserved (must be zero)
+ * @pa: physical address
  *
  * This descriptor is not a resource entry by itself; it is part of the
  * vdev resource type (see below).
@@ -255,7 +255,7 @@ struct fw_rsc_vdev_vring {
        u32 align;
        u32 num;
        u32 notifyid;
-       u32 reserved;
+       u32 pa;
 } __packed;
 
 /**
@@ -409,7 +409,6 @@ enum rproc_crash_type {
  * @max_notifyid: largest allocated notify id.
  * @table_ptr: pointer to the resource table in effect
  * @cached_table: copy of the resource table
- * @table_csum: checksum of the resource table
  * @has_iommu: flag to indicate if remote processor is behind an MMU
  */
 struct rproc {
@@ -435,14 +434,14 @@ struct rproc {
        struct idr notifyids;
        int index;
        struct work_struct crash_handler;
-       unsigned crash_cnt;
+       unsigned int crash_cnt;
        struct completion crash_comp;
        bool recovery_disabled;
        int max_notifyid;
        struct resource_table *table_ptr;
        struct resource_table *cached_table;
-       u32 table_csum;
        bool has_iommu;
+       bool auto_boot;
 };
 
 /* we currently support only two vrings per rvdev */
@@ -489,8 +488,8 @@ struct rproc_vdev {
 
 struct rproc *rproc_get_by_phandle(phandle phandle);
 struct rproc *rproc_alloc(struct device *dev, const char *name,
-                               const struct rproc_ops *ops,
-                               const char *firmware, int len);
+                         const struct rproc_ops *ops,
+                         const char *firmware, int len);
 void rproc_put(struct rproc *rproc);
 int rproc_add(struct rproc *rproc);
 int rproc_del(struct rproc *rproc);
index ada50ff36da0d6c73d537cc2bab7bc2673bd6957..452d393cc8dd9b5fccf286c132398819711de71b 100644 (file)
 #include <linux/kref.h>
 #include <linux/mutex.h>
 
-/* The feature bitmap for virtio rpmsg */
-#define VIRTIO_RPMSG_F_NS      0 /* RP supports name service notifications */
+#define RPMSG_ADDR_ANY         0xFFFFFFFF
+
+struct rpmsg_device;
+struct rpmsg_endpoint;
+struct rpmsg_device_ops;
+struct rpmsg_endpoint_ops;
 
 /**
- * struct rpmsg_hdr - common header for all rpmsg messages
- * @src: source address
+ * struct rpmsg_channel_info - channel info representation
+ * @name: name of service
+ * @src: local address
  * @dst: destination address
- * @reserved: reserved for future use
- * @len: length of payload (in bytes)
- * @flags: message flags
- * @data: @len bytes of message payload data
- *
- * Every message sent(/received) on the rpmsg bus begins with this header.
  */
-struct rpmsg_hdr {
+struct rpmsg_channel_info {
+       char name[RPMSG_NAME_SIZE];
        u32 src;
        u32 dst;
-       u32 reserved;
-       u16 len;
-       u16 flags;
-       u8 data[0];
-} __packed;
-
-/**
- * struct rpmsg_ns_msg - dynamic name service announcement message
- * @name: name of remote service that is published
- * @addr: address of remote service that is published
- * @flags: indicates whether service is created or destroyed
- *
- * This message is sent across to publish a new service, or announce
- * about its removal. When we receive these messages, an appropriate
- * rpmsg channel (i.e device) is created/destroyed. In turn, the ->probe()
- * or ->remove() handler of the appropriate rpmsg driver will be invoked
- * (if/as-soon-as one is registered).
- */
-struct rpmsg_ns_msg {
-       char name[RPMSG_NAME_SIZE];
-       u32 addr;
-       u32 flags;
-} __packed;
-
-/**
- * enum rpmsg_ns_flags - dynamic name service announcement flags
- *
- * @RPMSG_NS_CREATE: a new remote service was just created
- * @RPMSG_NS_DESTROY: a known remote service was just destroyed
- */
-enum rpmsg_ns_flags {
-       RPMSG_NS_CREATE         = 0,
-       RPMSG_NS_DESTROY        = 1,
 };
 
-#define RPMSG_ADDR_ANY         0xFFFFFFFF
-
-struct virtproc_info;
-
 /**
- * rpmsg_channel - devices that belong to the rpmsg bus are called channels
- * @vrp: the remote processor this channel belongs to
+ * rpmsg_device - device that belong to the rpmsg bus
  * @dev: the device struct
  * @id: device id (used to match between rpmsg drivers and devices)
  * @src: local address
@@ -107,17 +69,18 @@ struct virtproc_info;
  * @ept: the rpmsg endpoint of this channel
  * @announce: if set, rpmsg will announce the creation/removal of this channel
  */
-struct rpmsg_channel {
-       struct virtproc_info *vrp;
+struct rpmsg_device {
        struct device dev;
        struct rpmsg_device_id id;
        u32 src;
        u32 dst;
        struct rpmsg_endpoint *ept;
        bool announce;
+
+       const struct rpmsg_device_ops *ops;
 };
 
-typedef void (*rpmsg_rx_cb_t)(struct rpmsg_channel *, void *, int, void *, u32);
+typedef int (*rpmsg_rx_cb_t)(struct rpmsg_device *, void *, int, void *, u32);
 
 /**
  * struct rpmsg_endpoint - binds a local rpmsg address to its user
@@ -143,12 +106,14 @@ typedef void (*rpmsg_rx_cb_t)(struct rpmsg_channel *, void *, int, void *, u32);
  * create additional endpoints by themselves (see rpmsg_create_ept()).
  */
 struct rpmsg_endpoint {
-       struct rpmsg_channel *rpdev;
+       struct rpmsg_device *rpdev;
        struct kref refcount;
        rpmsg_rx_cb_t cb;
        struct mutex cb_lock;
        u32 addr;
        void *priv;
+
+       const struct rpmsg_endpoint_ops *ops;
 };
 
 /**
@@ -162,20 +127,19 @@ struct rpmsg_endpoint {
 struct rpmsg_driver {
        struct device_driver drv;
        const struct rpmsg_device_id *id_table;
-       int (*probe)(struct rpmsg_channel *dev);
-       void (*remove)(struct rpmsg_channel *dev);
-       void (*callback)(struct rpmsg_channel *, void *, int, void *, u32);
+       int (*probe)(struct rpmsg_device *dev);
+       void (*remove)(struct rpmsg_device *dev);
+       int (*callback)(struct rpmsg_device *, void *, int, void *, u32);
 };
 
-int register_rpmsg_device(struct rpmsg_channel *dev);
-void unregister_rpmsg_device(struct rpmsg_channel *dev);
+int register_rpmsg_device(struct rpmsg_device *dev);
+void unregister_rpmsg_device(struct rpmsg_device *dev);
 int __register_rpmsg_driver(struct rpmsg_driver *drv, struct module *owner);
 void unregister_rpmsg_driver(struct rpmsg_driver *drv);
 void rpmsg_destroy_ept(struct rpmsg_endpoint *);
-struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_channel *,
-                               rpmsg_rx_cb_t cb, void *priv, u32 addr);
-int
-rpmsg_send_offchannel_raw(struct rpmsg_channel *, u32, u32, void *, int, bool);
+struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_device *,
+                                       rpmsg_rx_cb_t cb, void *priv,
+                                       struct rpmsg_channel_info chinfo);
 
 /* use a macro to avoid include chaining to get THIS_MODULE */
 #define register_rpmsg_driver(drv) \
@@ -193,156 +157,14 @@ rpmsg_send_offchannel_raw(struct rpmsg_channel *, u32, u32, void *, int, bool);
        module_driver(__rpmsg_driver, register_rpmsg_driver, \
                        unregister_rpmsg_driver)
 
-/**
- * rpmsg_send() - send a message across to the remote processor
- * @rpdev: the rpmsg channel
- * @data: payload of message
- * @len: length of payload
- *
- * This function sends @data of length @len on the @rpdev channel.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to, using @rpdev's source and destination addresses.
- * In case there are no TX buffers available, the function will block until
- * one becomes available, or a timeout of 15 seconds elapses. When the latter
- * happens, -ERESTARTSYS is returned.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline int rpmsg_send(struct rpmsg_channel *rpdev, void *data, int len)
-{
-       u32 src = rpdev->src, dst = rpdev->dst;
-
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
-}
-
-/**
- * rpmsg_sendto() - send a message across to the remote processor, specify dst
- * @rpdev: the rpmsg channel
- * @data: payload of message
- * @len: length of payload
- * @dst: destination address
- *
- * This function sends @data of length @len to the remote @dst address.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to, using @rpdev's source address.
- * In case there are no TX buffers available, the function will block until
- * one becomes available, or a timeout of 15 seconds elapses. When the latter
- * happens, -ERESTARTSYS is returned.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline
-int rpmsg_sendto(struct rpmsg_channel *rpdev, void *data, int len, u32 dst)
-{
-       u32 src = rpdev->src;
-
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
-}
-
-/**
- * rpmsg_send_offchannel() - send a message using explicit src/dst addresses
- * @rpdev: the rpmsg channel
- * @src: source address
- * @dst: destination address
- * @data: payload of message
- * @len: length of payload
- *
- * This function sends @data of length @len to the remote @dst address,
- * and uses @src as the source address.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to.
- * In case there are no TX buffers available, the function will block until
- * one becomes available, or a timeout of 15 seconds elapses. When the latter
- * happens, -ERESTARTSYS is returned.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline
-int rpmsg_send_offchannel(struct rpmsg_channel *rpdev, u32 src, u32 dst,
-                                                       void *data, int len)
-{
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, true);
-}
-
-/**
- * rpmsg_send() - send a message across to the remote processor
- * @rpdev: the rpmsg channel
- * @data: payload of message
- * @len: length of payload
- *
- * This function sends @data of length @len on the @rpdev channel.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to, using @rpdev's source and destination addresses.
- * In case there are no TX buffers available, the function will immediately
- * return -ENOMEM without waiting until one becomes available.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline
-int rpmsg_trysend(struct rpmsg_channel *rpdev, void *data, int len)
-{
-       u32 src = rpdev->src, dst = rpdev->dst;
+int rpmsg_send(struct rpmsg_endpoint *ept, void *data, int len);
+int rpmsg_sendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
+int rpmsg_send_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                         void *data, int len);
 
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
-}
-
-/**
- * rpmsg_sendto() - send a message across to the remote processor, specify dst
- * @rpdev: the rpmsg channel
- * @data: payload of message
- * @len: length of payload
- * @dst: destination address
- *
- * This function sends @data of length @len to the remote @dst address.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to, using @rpdev's source address.
- * In case there are no TX buffers available, the function will immediately
- * return -ENOMEM without waiting until one becomes available.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline
-int rpmsg_trysendto(struct rpmsg_channel *rpdev, void *data, int len, u32 dst)
-{
-       u32 src = rpdev->src;
-
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
-}
-
-/**
- * rpmsg_send_offchannel() - send a message using explicit src/dst addresses
- * @rpdev: the rpmsg channel
- * @src: source address
- * @dst: destination address
- * @data: payload of message
- * @len: length of payload
- *
- * This function sends @data of length @len to the remote @dst address,
- * and uses @src as the source address.
- * The message will be sent to the remote processor which the @rpdev
- * channel belongs to.
- * In case there are no TX buffers available, the function will immediately
- * return -ENOMEM without waiting until one becomes available.
- *
- * Can only be called from process context (for now).
- *
- * Returns 0 on success and an appropriate error value on failure.
- */
-static inline
-int rpmsg_trysend_offchannel(struct rpmsg_channel *rpdev, u32 src, u32 dst,
-                                                       void *data, int len)
-{
-       return rpmsg_send_offchannel_raw(rpdev, src, dst, data, len, false);
-}
+int rpmsg_trysend(struct rpmsg_endpoint *ept, void *data, int len);
+int rpmsg_trysendto(struct rpmsg_endpoint *ept, void *data, int len, u32 dst);
+int rpmsg_trysend_offchannel(struct rpmsg_endpoint *ept, u32 src, u32 dst,
+                            void *data, int len);
 
 #endif /* _LINUX_RPMSG_H */
index d0e249c9066868ea8c121860b4da493405e710ac..f161dfd3e70a7aaf575e022f4390762f0608a9bc 100644 (file)
 #define MSG            "hello world!"
 #define MSG_LIMIT      100
 
-static void rpmsg_sample_cb(struct rpmsg_channel *rpdev, void *data, int len,
+struct instance_data {
+       int rx_count;
+};
+
+static int rpmsg_sample_cb(struct rpmsg_device *rpdev, void *data, int len,
                                                void *priv, u32 src)
 {
        int ret;
-       static int rx_count;
+       struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
 
-       dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n", ++rx_count, src);
+       dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n",
+                ++idata->rx_count, src);
 
        print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1,
                       data, len,  true);
 
        /* samples should not live forever */
-       if (rx_count >= MSG_LIMIT) {
+       if (idata->rx_count >= MSG_LIMIT) {
                dev_info(&rpdev->dev, "goodbye!\n");
-               return;
+               return 0;
        }
 
        /* send a new message now */
-       ret = rpmsg_send(rpdev, MSG, strlen(MSG));
+       ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
        if (ret)
                dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
+
+       return 0;
 }
 
-static int rpmsg_sample_probe(struct rpmsg_channel *rpdev)
+static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
 {
        int ret;
+       struct instance_data *idata;
 
        dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
                                        rpdev->src, rpdev->dst);
 
+       idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
+       if (!idata)
+               return -ENOMEM;
+
+       dev_set_drvdata(&rpdev->dev, idata);
+
        /* send a message to our remote processor */
-       ret = rpmsg_send(rpdev, MSG, strlen(MSG));
+       ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
        if (ret) {
                dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
                return ret;
@@ -64,7 +78,7 @@ static int rpmsg_sample_probe(struct rpmsg_channel *rpdev)
        return 0;
 }
 
-static void rpmsg_sample_remove(struct rpmsg_channel *rpdev)
+static void rpmsg_sample_remove(struct rpmsg_device *rpdev)
 {
        dev_info(&rpdev->dev, "rpmsg sample client driver is removed\n");
 }
This page took 0.095521 seconds and 5 git commands to generate.