summaryrefslogtreecommitdiff
path: root/drivers/remoteproc
diff options
context:
space:
mode:
authorDavid Woodhouse <David.Woodhouse@intel.com>2012-10-09 15:03:21 +0100
committerDavid Woodhouse <David.Woodhouse@intel.com>2012-10-09 15:04:25 +0100
commitffe315012510165ce82e4dd4767f0a5dba9edbf7 (patch)
treef601cd980af9d0ced5ca9aedecef4fa0d2ca0e15 /drivers/remoteproc
parente2d3a35ee427aaba99b6c68a56609ce276c51270 (diff)
parent4a8e43feeac7996b8de2d5b2823e316917493df4 (diff)
Merge tag 'disintegrate-mtd-20121009' of git://git.infradead.org/users/dhowells/linux-headers
UAPI Disintegration 2012-10-09 Conflicts: MAINTAINERS arch/arm/configs/bcmring_defconfig arch/arm/mach-imx/clk-imx51-imx53.c drivers/mtd/nand/Kconfig drivers/mtd/nand/bcm_umi_nand.c drivers/mtd/nand/nand_bcm_umi.h drivers/mtd/nand/orion_nand.c
Diffstat (limited to 'drivers/remoteproc')
-rw-r--r--drivers/remoteproc/Kconfig14
-rw-r--r--drivers/remoteproc/Makefile1
-rw-r--r--drivers/remoteproc/omap_remoteproc.c5
-rw-r--r--drivers/remoteproc/remoteproc_core.c209
-rw-r--r--drivers/remoteproc/remoteproc_debugfs.c85
-rw-r--r--drivers/remoteproc/remoteproc_internal.h1
-rw-r--r--drivers/remoteproc/remoteproc_virtio.c5
-rw-r--r--drivers/remoteproc/ste_modem_rproc.c322
8 files changed, 598 insertions, 44 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index f8d818abf98c..96ce101b9067 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -4,11 +4,14 @@ menu "Remoteproc drivers (EXPERIMENTAL)"
config REMOTEPROC
tristate
depends on EXPERIMENTAL
+ depends on HAS_DMA
select FW_CONFIG
+ select VIRTIO
config OMAP_REMOTEPROC
tristate "OMAP remoteproc support"
depends on EXPERIMENTAL
+ depends on HAS_DMA
depends on ARCH_OMAP4
depends on OMAP_IOMMU
select REMOTEPROC
@@ -27,4 +30,15 @@ config OMAP_REMOTEPROC
It's safe to say n here if you're not interested in multimedia
offloading or just want a bare minimum kernel.
+config STE_MODEM_RPROC
+ tristate "STE-Modem remoteproc support"
+ depends on EXPERIMENTAL
+ depends on HAS_DMA
+ select REMOTEPROC
+ default n
+ help
+ Say y or m here to support STE-Modem shared memory driver.
+ This can be either built-in or a loadable module.
+ If unsure say N.
+
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 934ce6e2c66b..391b65181c05 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -8,3 +8,4 @@ remoteproc-y += remoteproc_debugfs.o
remoteproc-y += remoteproc_virtio.o
remoteproc-y += remoteproc_elf_loader.o
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
+obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o
diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c
index a1f7ac1f8cf6..32c289c2ba13 100644
--- a/drivers/remoteproc/omap_remoteproc.c
+++ b/drivers/remoteproc/omap_remoteproc.c
@@ -29,7 +29,7 @@
#include <linux/remoteproc.h>
#include <plat/mailbox.h>
-#include <plat/remoteproc.h>
+#include <linux/platform_data/remoteproc-omap.h>
#include "omap_remoteproc.h"
#include "remoteproc_internal.h"
@@ -116,6 +116,9 @@ static int omap_rproc_start(struct rproc *rproc)
struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
int ret;
+ if (pdata->set_bootaddr)
+ pdata->set_bootaddr(rproc->bootaddr);
+
oproc->nb.notifier_call = omap_rproc_mbox_callback;
/* every omap rproc is assigned a mailbox instance for messaging */
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index d5c2dbfc7443..dd3bfaf1ad40 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -50,6 +50,18 @@ typedef int (*rproc_handle_resource_t)(struct rproc *rproc, void *, int avail);
/* Unique indices for remoteproc devices */
static DEFINE_IDA(rproc_dev_index);
+static const char * const rproc_crash_names[] = {
+ [RPROC_MMUFAULT] = "mmufault",
+};
+
+/* translate rproc_crash_type to string */
+static const char *rproc_crash_to_string(enum rproc_crash_type type)
+{
+ if (type < ARRAY_SIZE(rproc_crash_names))
+ return rproc_crash_names[type];
+ return "unkown";
+}
+
/*
* This is the IOMMU fault handler we register with the IOMMU API
* (when relevant; not all remote processors access memory through
@@ -57,18 +69,19 @@ static DEFINE_IDA(rproc_dev_index);
*
* IOMMU core will invoke this handler whenever the remote processor
* will try to access an unmapped device address.
- *
- * Currently this is mostly a stub, but it will be later used to trigger
- * the recovery of the remote processor.
*/
static int rproc_iommu_fault(struct iommu_domain *domain, struct device *dev,
unsigned long iova, int flags, void *token)
{
+ struct rproc *rproc = token;
+
dev_err(dev, "iommu fault: da 0x%lx flags 0x%x\n", iova, flags);
+ rproc_report_crash(rproc, RPROC_MMUFAULT);
+
/*
* Let the iommu core know we're not really handling this fault;
- * we just plan to use this as a recovery trigger.
+ * we just used it as a recovery trigger.
*/
return -ENOSYS;
}
@@ -215,8 +228,11 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i)
return ret;
}
- dev_dbg(dev, "vring%d: va %p dma %x size %x idr %d\n", i, va,
- dma, size, notifyid);
+ /* Store largest notifyid */
+ rproc->max_notifyid = max(rproc->max_notifyid, notifyid);
+
+ dev_dbg(dev, "vring%d: va %p dma %llx size %x idr %d\n", i, va,
+ (unsigned long long)dma, size, notifyid);
rvring->va = va;
rvring->dma = dma;
@@ -256,13 +272,25 @@ rproc_parse_vring(struct rproc_vdev *rvdev, struct fw_rsc_vdev *rsc, int i)
return 0;
}
+static int rproc_max_notifyid(int id, void *p, void *data)
+{
+ int *maxid = data;
+ *maxid = max(*maxid, id);
+ return 0;
+}
+
void rproc_free_vring(struct rproc_vring *rvring)
{
int size = PAGE_ALIGN(vring_size(rvring->len, rvring->align));
struct rproc *rproc = rvring->rvdev->rproc;
+ int maxid = 0;
dma_free_coherent(rproc->dev.parent, size, rvring->va, rvring->dma);
idr_remove(&rproc->notifyids, rvring->notifyid);
+
+ /* Find the largest remaining notifyid */
+ idr_for_each(&rproc->notifyids, rproc_max_notifyid, &maxid);
+ rproc->max_notifyid = maxid;
}
/**
@@ -545,17 +573,10 @@ static int rproc_handle_carveout(struct rproc *rproc,
dev_dbg(dev, "carveout rsc: da %x, pa %x, len %x, flags %x\n",
rsc->da, rsc->pa, rsc->len, rsc->flags);
- mapping = kzalloc(sizeof(*mapping), GFP_KERNEL);
- if (!mapping) {
- dev_err(dev, "kzalloc mapping failed\n");
- return -ENOMEM;
- }
-
carveout = kzalloc(sizeof(*carveout), GFP_KERNEL);
if (!carveout) {
dev_err(dev, "kzalloc carveout failed\n");
- ret = -ENOMEM;
- goto free_mapping;
+ return -ENOMEM;
}
va = dma_alloc_coherent(dev->parent, rsc->len, &dma, GFP_KERNEL);
@@ -565,7 +586,8 @@ static int rproc_handle_carveout(struct rproc *rproc,
goto free_carv;
}
- dev_dbg(dev, "carveout va %p, dma %x, len 0x%x\n", va, dma, rsc->len);
+ dev_dbg(dev, "carveout va %p, dma %llx, len 0x%x\n", va,
+ (unsigned long long)dma, rsc->len);
/*
* Ok, this is non-standard.
@@ -585,11 +607,18 @@ static int rproc_handle_carveout(struct rproc *rproc,
* physical address in this case.
*/
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);
if (ret) {
dev_err(dev, "iommu_map failed: %d\n", ret);
- goto dma_free;
+ goto free_mapping;
}
/*
@@ -603,7 +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%x\n", rsc->da, dma);
+ dev_dbg(dev, "carveout mapped 0x%x to 0x%llx\n",
+ rsc->da, (unsigned long long)dma);
}
/*
@@ -634,12 +664,12 @@ static int rproc_handle_carveout(struct rproc *rproc,
return 0;
+free_mapping:
+ kfree(mapping);
dma_free:
dma_free_coherent(dev->parent, rsc->len, va, dma);
free_carv:
kfree(carveout);
-free_mapping:
- kfree(mapping);
return ret;
}
@@ -871,6 +901,91 @@ out:
complete_all(&rproc->firmware_loading_complete);
}
+static int rproc_add_virtio_devices(struct rproc *rproc)
+{
+ int ret;
+
+ /* rproc_del() calls must wait until async loader completes */
+ init_completion(&rproc->firmware_loading_complete);
+
+ /*
+ * We must retrieve early virtio configuration info from
+ * the firmware (e.g. whether to register a virtio device,
+ * what virtio features does it support, ...).
+ *
+ * We're initiating an asynchronous firmware loading, so we can
+ * be built-in kernel code, without hanging the boot process.
+ */
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ rproc->firmware, &rproc->dev, GFP_KERNEL,
+ rproc, rproc_fw_config_virtio);
+ if (ret < 0) {
+ dev_err(&rproc->dev, "request_firmware_nowait err: %d\n", ret);
+ complete_all(&rproc->firmware_loading_complete);
+ }
+
+ return ret;
+}
+
+/**
+ * rproc_trigger_recovery() - recover a remoteproc
+ * @rproc: the remote processor
+ *
+ * The recovery is done by reseting all the virtio devices, that way all the
+ * rpmsg drivers will be reseted along with the remote processor making the
+ * remoteproc functional again.
+ *
+ * This function can sleep, so it cannot be called from atomic context.
+ */
+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);
+
+ /* wait until there is no more rproc users */
+ wait_for_completion(&rproc->crash_comp);
+
+ return rproc_add_virtio_devices(rproc);
+}
+
+/**
+ * rproc_crash_handler_work() - handle a crash
+ *
+ * This function needs to handle everything related to a crash, like cpu
+ * registers and stack dump, information to help to debug the fatal error, etc.
+ */
+static void rproc_crash_handler_work(struct work_struct *work)
+{
+ struct rproc *rproc = container_of(work, struct rproc, crash_handler);
+ struct device *dev = &rproc->dev;
+
+ dev_dbg(dev, "enter %s\n", __func__);
+
+ mutex_lock(&rproc->lock);
+
+ if (rproc->state == RPROC_CRASHED || rproc->state == RPROC_OFFLINE) {
+ /* handle only the first crash detected */
+ mutex_unlock(&rproc->lock);
+ return;
+ }
+
+ rproc->state = RPROC_CRASHED;
+ dev_err(dev, "handling crash #%u in %s\n", ++rproc->crash_cnt,
+ rproc->name);
+
+ mutex_unlock(&rproc->lock);
+
+ if (!rproc->recovery_disabled)
+ rproc_trigger_recovery(rproc);
+}
+
/**
* rproc_boot() - boot a remote processor
* @rproc: handle of a remote processor
@@ -992,6 +1107,10 @@ void rproc_shutdown(struct rproc *rproc)
rproc_disable_iommu(rproc);
+ /* if in crash state, unlock crash handler */
+ if (rproc->state == RPROC_CRASHED)
+ complete_all(&rproc->crash_comp);
+
rproc->state = RPROC_OFFLINE;
dev_info(dev, "stopped remote processor %s\n", rproc->name);
@@ -1026,7 +1145,7 @@ EXPORT_SYMBOL(rproc_shutdown);
int rproc_add(struct rproc *rproc)
{
struct device *dev = &rproc->dev;
- int ret = 0;
+ int ret;
ret = device_add(dev);
if (ret < 0)
@@ -1040,26 +1159,7 @@ int rproc_add(struct rproc *rproc)
/* create debugfs entries */
rproc_create_debug_dir(rproc);
- /* rproc_del() calls must wait until async loader completes */
- init_completion(&rproc->firmware_loading_complete);
-
- /*
- * We must retrieve early virtio configuration info from
- * the firmware (e.g. whether to register a virtio device,
- * what virtio features does it support, ...).
- *
- * We're initiating an asynchronous firmware loading, so we can
- * be built-in kernel code, without hanging the boot process.
- */
- ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
- rproc->firmware, dev, GFP_KERNEL,
- rproc, rproc_fw_config_virtio);
- if (ret < 0) {
- dev_err(dev, "request_firmware_nowait failed: %d\n", ret);
- complete_all(&rproc->firmware_loading_complete);
- }
-
- return ret;
+ return rproc_add_virtio_devices(rproc);
}
EXPORT_SYMBOL(rproc_add);
@@ -1165,6 +1265,9 @@ struct rproc *rproc_alloc(struct device *dev, const char *name,
INIT_LIST_HEAD(&rproc->traces);
INIT_LIST_HEAD(&rproc->rvdevs);
+ INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work);
+ init_completion(&rproc->crash_comp);
+
rproc->state = RPROC_OFFLINE;
return rproc;
@@ -1221,6 +1324,32 @@ int rproc_del(struct rproc *rproc)
}
EXPORT_SYMBOL(rproc_del);
+/**
+ * rproc_report_crash() - rproc crash reporter function
+ * @rproc: remote processor
+ * @type: crash type
+ *
+ * This function must be called every time a crash is detected by the low-level
+ * drivers implementing a specific remoteproc. This should not be called from a
+ * non-remoteproc driver.
+ *
+ * This function can be called from atomic/interrupt context.
+ */
+void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type)
+{
+ if (!rproc) {
+ pr_err("NULL rproc pointer\n");
+ return;
+ }
+
+ dev_err(&rproc->dev, "crash detected in %s: type %s\n",
+ rproc->name, rproc_crash_to_string(type));
+
+ /* create a new task to handle the error */
+ schedule_work(&rproc->crash_handler);
+}
+EXPORT_SYMBOL(rproc_report_crash);
+
static int __init remoteproc_init(void)
{
rproc_init_debugfs();
diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c
index 03833850f214..157a57309601 100644
--- a/drivers/remoteproc/remoteproc_debugfs.c
+++ b/drivers/remoteproc/remoteproc_debugfs.c
@@ -28,6 +28,9 @@
#include <linux/debugfs.h>
#include <linux/remoteproc.h>
#include <linux/device.h>
+#include <linux/uaccess.h>
+
+#include "remoteproc_internal.h"
/* remoteproc debugfs parent dir */
static struct dentry *rproc_dbg;
@@ -79,7 +82,7 @@ static ssize_t rproc_state_read(struct file *filp, char __user *userbuf,
state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
- i = snprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
+ i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
rproc->state);
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
@@ -100,7 +103,7 @@ static ssize_t rproc_name_read(struct file *filp, char __user *userbuf,
char buf[100];
int i;
- i = snprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
+ i = scnprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
}
@@ -111,6 +114,82 @@ static const struct file_operations rproc_name_ops = {
.llseek = generic_file_llseek,
};
+/* expose recovery flag via debugfs */
+static ssize_t rproc_recovery_read(struct file *filp, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct rproc *rproc = filp->private_data;
+ char *buf = rproc->recovery_disabled ? "disabled\n" : "enabled\n";
+
+ return simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
+}
+
+/*
+ * By writing to the 'recovery' debugfs entry, we control the behavior of the
+ * recovery mechanism dynamically. The default value of this entry is "enabled".
+ *
+ * The 'recovery' debugfs entry supports these commands:
+ *
+ * enabled: When enabled, the remote processor will be automatically
+ * recovered whenever it crashes. Moreover, if the remote
+ * processor crashes while recovery is disabled, it will
+ * be automatically recovered too as soon as recovery is enabled.
+ *
+ * disabled: When disabled, a remote processor will remain in a crashed
+ * state if it crashes. This is useful for debugging purposes;
+ * without it, debugging a crash is substantially harder.
+ *
+ * recover: This function will trigger an immediate recovery if the
+ * remote processor is in a crashed state, without changing
+ * or checking the recovery state (enabled/disabled).
+ * This is useful during debugging sessions, when one expects
+ * additional crashes to happen after enabling recovery. In this
+ * case, enabling recovery will make it hard to debug subsequent
+ * crashes, so it's recommended to keep recovery disabled, and
+ * instead use the "recover" command as needed.
+ */
+static ssize_t
+rproc_recovery_write(struct file *filp, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct rproc *rproc = filp->private_data;
+ char buf[10];
+ int ret;
+
+ if (count > sizeof(buf))
+ return count;
+
+ ret = copy_from_user(buf, user_buf, count);
+ if (ret)
+ return -EFAULT;
+
+ /* remove end of line */
+ if (buf[count - 1] == '\n')
+ buf[count - 1] = '\0';
+
+ if (!strncmp(buf, "enabled", count)) {
+ rproc->recovery_disabled = false;
+ /* if rproc has crashed, trigger recovery */
+ if (rproc->state == RPROC_CRASHED)
+ rproc_trigger_recovery(rproc);
+ } else if (!strncmp(buf, "disabled", count)) {
+ rproc->recovery_disabled = true;
+ } else if (!strncmp(buf, "recover", count)) {
+ /* if rproc has crashed, trigger recovery */
+ if (rproc->state == RPROC_CRASHED)
+ rproc_trigger_recovery(rproc);
+ }
+
+ return count;
+}
+
+static const struct file_operations rproc_recovery_ops = {
+ .read = rproc_recovery_read,
+ .write = rproc_recovery_write,
+ .open = simple_open,
+ .llseek = generic_file_llseek,
+};
+
void rproc_remove_trace_file(struct dentry *tfile)
{
debugfs_remove(tfile);
@@ -154,6 +233,8 @@ void rproc_create_debug_dir(struct rproc *rproc)
rproc, &rproc_name_ops);
debugfs_create_file("state", 0400, rproc->dbg_dir,
rproc, &rproc_state_ops);
+ debugfs_create_file("recovery", 0400, rproc->dbg_dir,
+ rproc, &rproc_recovery_ops);
}
void __init rproc_init_debugfs(void)
diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h
index a690ebe7aa51..7bb66482d061 100644
--- a/drivers/remoteproc/remoteproc_internal.h
+++ b/drivers/remoteproc/remoteproc_internal.h
@@ -63,6 +63,7 @@ void rproc_free_vring(struct rproc_vring *rvring);
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i);
void *rproc_da_to_va(struct rproc *rproc, u64 da, int len);
+int rproc_trigger_recovery(struct rproc *rproc);
static inline
int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw)
diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c
index 3541b4492f64..e7a4780e93db 100644
--- a/drivers/remoteproc/remoteproc_virtio.c
+++ b/drivers/remoteproc/remoteproc_virtio.c
@@ -84,6 +84,9 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
if (id >= ARRAY_SIZE(rvdev->vring))
return ERR_PTR(-EINVAL);
+ if (!name)
+ return NULL;
+
ret = rproc_alloc_vring(rvdev, id);
if (ret)
return ERR_PTR(ret);
@@ -103,7 +106,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
* 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(len, rvring->align, vdev, false, addr,
+ vq = vring_new_virtqueue(id, len, rvring->align, vdev, false, addr,
rproc_virtio_notify, callback, name);
if (!vq) {
dev_err(dev, "vring_new_virtqueue %s failed\n", name);
diff --git a/drivers/remoteproc/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c
new file mode 100644
index 000000000000..a7743c069339
--- /dev/null
+++ b/drivers/remoteproc/ste_modem_rproc.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2012
+ * Author: Sjur Brændeland <sjur.brandeland@stericsson.com>
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/remoteproc.h>
+#include <linux/ste_modem_shm.h>
+#include "remoteproc_internal.h"
+
+#define SPROC_FW_SIZE (50 * 4096)
+#define SPROC_MAX_TOC_ENTRIES 32
+#define SPROC_MAX_NOTIFY_ID 14
+#define SPROC_RESOURCE_NAME "rsc-table"
+#define SPROC_MODEM_NAME "ste-modem"
+#define SPROC_MODEM_FIRMWARE SPROC_MODEM_NAME "-fw.bin"
+
+#define sproc_dbg(sproc, fmt, ...) \
+ dev_dbg(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
+#define sproc_err(sproc, fmt, ...) \
+ dev_err(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
+
+/* STE-modem control structure */
+struct sproc {
+ struct rproc *rproc;
+ struct ste_modem_device *mdev;
+ int error;
+ void *fw_addr;
+ size_t fw_size;
+ dma_addr_t fw_dma_addr;
+};
+
+/* STE-Modem firmware entry */
+struct ste_toc_entry {
+ __le32 start;
+ __le32 size;
+ __le32 flags;
+ __le32 entry_point;
+ __le32 load_addr;
+ char name[12];
+};
+
+/*
+ * The Table Of Content is located at the start of the firmware image and
+ * at offset zero in the shared memory region. The resource table typically
+ * contains the initial boot image (boot strap) and other information elements
+ * such as remoteproc resource table. Each entry is identified by a unique
+ * name.
+ */
+struct ste_toc {
+ struct ste_toc_entry table[SPROC_MAX_TOC_ENTRIES];
+};
+
+/* Loads the firmware to shared memory. */
+static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw)
+{
+ struct sproc *sproc = rproc->priv;
+
+ memcpy(sproc->fw_addr, fw->data, fw->size);
+
+ return 0;
+}
+
+/* Find the entry for resource table in the Table of Content */
+static struct ste_toc_entry *sproc_find_rsc_entry(const struct firmware *fw)
+{
+ int i;
+ struct ste_toc *toc;
+
+ if (!fw)
+ return NULL;
+
+ toc = (void *)fw->data;
+
+ /* Search the table for the resource table */
+ for (i = 0; i < SPROC_MAX_TOC_ENTRIES &&
+ toc->table[i].start != 0xffffffff; i++) {
+
+ if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME,
+ sizeof(toc->table[i].name))) {
+ if (toc->table[i].start > fw->size)
+ return NULL;
+ return &toc->table[i];
+ }
+ }
+
+ return NULL;
+}
+
+/* Find the resource table inside the remote processor's firmware. */
+static struct resource_table *
+sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw,
+ int *tablesz)
+{
+ struct sproc *sproc = rproc->priv;
+ struct resource_table *table;
+ struct ste_toc_entry *entry;
+
+ entry = sproc_find_rsc_entry(fw);
+ if (!entry) {
+ sproc_err(sproc, "resource table not found in fw\n");
+ return NULL;
+ }
+
+ table = (void *)(fw->data + entry->start);
+
+ /* sanity check size and offset of resource table */
+ if (entry->start > SPROC_FW_SIZE ||
+ entry->size > SPROC_FW_SIZE ||
+ fw->size > SPROC_FW_SIZE ||
+ entry->start + entry->size > fw->size ||
+ sizeof(struct resource_table) > entry->size) {
+ sproc_err(sproc, "bad size of fw or resource table\n");
+ return NULL;
+ }
+
+ /* we don't support any version beyond the first */
+ if (table->ver != 1) {
+ sproc_err(sproc, "unsupported fw ver: %d\n", table->ver);
+ return NULL;
+ }
+
+ /* make sure reserved bytes are zeroes */
+ if (table->reserved[0] || table->reserved[1]) {
+ sproc_err(sproc, "non zero reserved bytes\n");
+ return NULL;
+ }
+
+ /* make sure the offsets array isn't truncated */
+ if (table->num > SPROC_MAX_TOC_ENTRIES ||
+ table->num * sizeof(table->offset[0]) +
+ sizeof(struct resource_table) > entry->size) {
+ sproc_err(sproc, "resource table incomplete\n");
+ return NULL;
+ }
+
+ /* If the fw size has grown, release the previous fw allocation */
+ if (SPROC_FW_SIZE < fw->size) {
+ sproc_err(sproc, "Insufficient space for fw (%d < %zd)\n",
+ SPROC_FW_SIZE, fw->size);
+ return NULL;
+ }
+
+ sproc->fw_size = fw->size;
+ *tablesz = entry->size;
+
+ return table;
+}
+
+/* STE modem firmware handler operations */
+const struct rproc_fw_ops sproc_fw_ops = {
+ .load = sproc_load_segments,
+ .find_rsc_table = sproc_find_rsc_table,
+};
+
+/* Kick the modem with specified notification id */
+static void sproc_kick(struct rproc *rproc, int vqid)
+{
+ struct sproc *sproc = rproc->priv;
+
+ sproc_dbg(sproc, "kick vqid:%d\n", vqid);
+
+ /*
+ * We need different notification IDs for RX and TX so add
+ * an offset on TX notification IDs.
+ */
+ sproc->mdev->ops.kick(sproc->mdev, vqid + SPROC_MAX_NOTIFY_ID);
+}
+
+/* Received a kick from a modem, kick the virtqueue */
+static void sproc_kick_callback(struct ste_modem_device *mdev, int vqid)
+{
+ struct sproc *sproc = mdev->drv_data;
+
+ if (rproc_vq_interrupt(sproc->rproc, vqid) == IRQ_NONE)
+ sproc_dbg(sproc, "no message was found in vqid %d\n", vqid);
+}
+
+struct ste_modem_dev_cb sproc_dev_cb = {
+ .kick = sproc_kick_callback,
+};
+
+/* Start the STE modem */
+static int sproc_start(struct rproc *rproc)
+{
+ struct sproc *sproc = rproc->priv;
+ int i, err;
+
+ sproc_dbg(sproc, "start ste-modem\n");
+
+ /* Sanity test the max_notifyid */
+ if (rproc->max_notifyid > SPROC_MAX_NOTIFY_ID) {
+ sproc_err(sproc, "Notification IDs too high:%d\n",
+ rproc->max_notifyid);
+ return -EINVAL;
+ }
+
+ /* Subscribe to notifications */
+ for (i = 0; i < rproc->max_notifyid; i++) {
+ err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i);
+ if (err) {
+ sproc_err(sproc,
+ "subscription of kicks failed:%d\n", err);
+ return err;
+ }
+ }
+
+ /* Request modem start-up*/
+ return sproc->mdev->ops.power(sproc->mdev, true);
+}
+
+/* Stop the STE modem */
+static int sproc_stop(struct rproc *rproc)
+{
+ struct sproc *sproc = rproc->priv;
+ sproc_dbg(sproc, "stop ste-modem\n");
+
+ return sproc->mdev->ops.power(sproc->mdev, false);
+}
+
+static struct rproc_ops sproc_ops = {
+ .start = sproc_start,
+ .stop = sproc_stop,
+ .kick = sproc_kick,
+};
+
+/* STE modem device is unregistered */
+static int sproc_drv_remove(struct platform_device *pdev)
+{
+ struct ste_modem_device *mdev =
+ container_of(pdev, struct ste_modem_device, pdev);
+ struct sproc *sproc = mdev->drv_data;
+
+ sproc_dbg(sproc, "remove ste-modem\n");
+
+ /* Reset device callback functions */
+ sproc->mdev->ops.setup(sproc->mdev, NULL);
+
+ /* Unregister as remoteproc device */
+ rproc_del(sproc->rproc);
+ rproc_put(sproc->rproc);
+
+ mdev->drv_data = NULL;
+
+ return 0;
+}
+
+/* Handle probe of a modem device */
+static int sproc_probe(struct platform_device *pdev)
+{
+ struct ste_modem_device *mdev =
+ container_of(pdev, struct ste_modem_device, pdev);
+ struct sproc *sproc;
+ struct rproc *rproc;
+ int err;
+
+ dev_dbg(&mdev->pdev.dev, "probe ste-modem\n");
+
+ if (!mdev->ops.setup || !mdev->ops.kick || !mdev->ops.kick_subscribe ||
+ !mdev->ops.power) {
+ dev_err(&mdev->pdev.dev, "invalid mdev ops\n");
+ return -EINVAL;
+ }
+
+ rproc = rproc_alloc(&mdev->pdev.dev, mdev->pdev.name, &sproc_ops,
+ SPROC_MODEM_FIRMWARE, sizeof(*sproc));
+ if (!rproc)
+ return -ENOMEM;
+
+ sproc = rproc->priv;
+ sproc->mdev = mdev;
+ sproc->rproc = rproc;
+ mdev->drv_data = sproc;
+
+ /* Provide callback functions to modem device */
+ sproc->mdev->ops.setup(sproc->mdev, &sproc_dev_cb);
+
+ /* Set the STE-modem specific firmware handler */
+ rproc->fw_ops = &sproc_fw_ops;
+
+ /*
+ * STE-modem requires the firmware to be located
+ * at the start of the shared memory region. So we need to
+ * reserve space for firmware at the start.
+ */
+ sproc->fw_addr = dma_alloc_coherent(rproc->dev.parent, SPROC_FW_SIZE,
+ &sproc->fw_dma_addr,
+ GFP_KERNEL);
+ if (!sproc->fw_addr) {
+ sproc_err(sproc, "Cannot allocate memory for fw\n");
+ err = -ENOMEM;
+ goto free_rproc;
+ }
+
+ /* Register as a remoteproc device */
+ err = rproc_add(rproc);
+ if (err)
+ goto free_rproc;
+
+ return 0;
+
+free_rproc:
+ /* Reset device data upon error */
+ mdev->drv_data = NULL;
+ rproc_put(rproc);
+ return err;
+}
+
+static struct platform_driver sproc_driver = {
+ .driver = {
+ .name = SPROC_MODEM_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = sproc_probe,
+ .remove = sproc_drv_remove,
+};
+
+module_platform_driver(sproc_driver);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("STE Modem driver using the Remote Processor Framework");