]> Pileus Git - ~andy/linux/commitdiff
Merge tag 'remoteproc-for-3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/ohad...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Oct 2012 16:11:57 +0000 (09:11 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Oct 2012 16:11:57 +0000 (09:11 -0700)
Pull remoteproc update from Ohad Ben-Cohen:

 - Remoteproc Recovery - by Fernando Guzman Lugo

   When a remote processor crash is detected, this mechanism will remove
   all virtio children devices, wait until their drivers let go, hard
   reset the remote processor and reload the firmware (resulting in the
   relevant virtio children devices re-added).  Essentially the entire
   software stack is reset, together with the relevant hardware, so
   users don't have to reset the entire phone.

 - STE Modem driver is added - by Sjur Brændeland

 - OMAP DSP boot address support is added - by Juan Gutierrez

 - A handful of fixes/cleanups - Sjur Brændeland, Dan Carpenter, Emil
   Goode

* tag 'remoteproc-for-3.7' of git://git.kernel.org/pub/scm/linux/kernel/git/ohad/remoteproc:
  remoteproc: Fix use of format specifyer
  remoteproc: fix a potential NULL-dereference on cleanup
  remoteproc: select VIRTIO to avoid build breakage
  remoteproc: return -EFAULT on copy_from_user failure
  remoteproc: snprintf() can return more than was printed
  remoteproc: Add STE modem driver
  remtoteproc: maintain max notifyid
  remoteproc: create a 'recovery' debugfs entry
  remoteproc: add actual recovery implementation
  remoteproc: add rproc_report_crash function to notify rproc crashes
  remoteproc: Add dependency to HAS_DMA
  remoteproc/omap: set bootaddr support

Documentation/remoteproc.txt
drivers/remoteproc/Kconfig
drivers/remoteproc/Makefile
drivers/remoteproc/omap_remoteproc.c
drivers/remoteproc/remoteproc_core.c
drivers/remoteproc/remoteproc_debugfs.c
drivers/remoteproc/remoteproc_internal.h
drivers/remoteproc/ste_modem_rproc.c [new file with mode: 0644]
include/linux/platform_data/remoteproc-omap.h
include/linux/remoteproc.h
include/linux/ste_modem_shm.h [new file with mode: 0644]

index 23a09b884bc76398a6a82bb68933dcba84b4231a..e6469fdcf89a0344b017f90c821019750d8e9039 100644 (file)
@@ -129,6 +129,13 @@ int dummy_rproc_example(struct rproc *my_rproc)
 
       Returns 0 on success and -EINVAL if @rproc isn't valid.
 
+  void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type)
+    - Report a crash in a remoteproc
+      This function must be called every time a crash is detected by the
+      platform specific rproc implementation. This should not be called from a
+      non-remoteproc driver. This function can be called from atomic/interrupt
+      context.
+
 5. Implementation callbacks
 
 These callbacks should be provided by platform-specific remoteproc
index f8d818abf98caf4dc8275a65e18695c41c639537..96ce101b906753c703cf88c1c76d6a7e9a33e826 100644 (file)
@@ -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
index 934ce6e2c66bd8fa7af09960983a69aadca834a2..391b65181c054c795a8e0e34b1092f4ddba969f4 100644 (file)
@@ -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
index b54504ee61f12287c25aa8bb715d9668e977ff20..32c289c2ba136a732e1cb508f06360fa5ae28c48 100644 (file)
@@ -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 */
index d5c2dbfc7443c0ca4ced101fc0ab5447b0bf1e8f..dd3bfaf1ad4075436c8bce652d48d80024d5c705 100644 (file)
@@ -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();
index 03833850f214ffb7755a9fd94e59b289f0f10521..157a573096011a07d7e30cd40ce88166b9465c28 100644 (file)
@@ -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)
index a690ebe7aa5116f7fe6fec787e548c84b0094048..7bb66482d061d33e41cd9f4e69fb97fe0fa91655 100644 (file)
@@ -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/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c
new file mode 100644 (file)
index 0000000..a7743c0
--- /dev/null
@@ -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");
index b10eac89e2e9c419f8dc82fe5cae5e444f5b6b0e..3c1c6444ec4b7f87c39ce8645441ba20bcc2cd77 100644 (file)
@@ -30,6 +30,7 @@ struct platform_device;
  * @ops: start/stop rproc handlers
  * @device_enable: omap-specific handler for enabling a device
  * @device_shutdown: omap-specific handler for shutting down a device
+ * @set_bootaddr: omap-specific handler for setting the rproc boot address
  */
 struct omap_rproc_pdata {
        const char *name;
@@ -40,6 +41,7 @@ struct omap_rproc_pdata {
        const struct rproc_ops *ops;
        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 131b53957b9f24d2127936ac86c5d0f7e34f766a..faf33324c78f9613526a66474670303ae5661aab 100644 (file)
@@ -360,6 +360,19 @@ enum rproc_state {
        RPROC_LAST      = 4,
 };
 
+/**
+ * enum rproc_crash_type - remote processor crash types
+ * @RPROC_MMUFAULT:    iommu fault
+ *
+ * Each element of the enum is used as an array index. So that, the value of
+ * the elements should be always something sane.
+ *
+ * Feel free to add more types when needed.
+ */
+enum rproc_crash_type {
+       RPROC_MMUFAULT,
+};
+
 /**
  * struct rproc - represents a physical remote processor device
  * @node: klist node of this rproc object
@@ -383,6 +396,11 @@ enum rproc_state {
  * @rvdevs: list of remote virtio devices
  * @notifyids: idr for dynamically assigning rproc-wide unique notify ids
  * @index: index of this rproc device
+ * @crash_handler: workqueue for handling a crash
+ * @crash_cnt: crash counter
+ * @crash_comp: completion used to sync crash handler and the rproc reload
+ * @recovery_disabled: flag that state if recovery was disabled
+ * @max_notifyid: largest allocated notify id.
  */
 struct rproc {
        struct klist_node node;
@@ -406,6 +424,11 @@ struct rproc {
        struct list_head rvdevs;
        struct idr notifyids;
        int index;
+       struct work_struct crash_handler;
+       unsigned crash_cnt;
+       struct completion crash_comp;
+       bool recovery_disabled;
+       int max_notifyid;
 };
 
 /* we currently support only two vrings per rvdev */
@@ -460,6 +483,7 @@ int rproc_del(struct rproc *rproc);
 
 int rproc_boot(struct rproc *rproc);
 void rproc_shutdown(struct rproc *rproc);
+void rproc_report_crash(struct rproc *rproc, enum rproc_crash_type type);
 
 static inline struct rproc_vdev *vdev_to_rvdev(struct virtio_device *vdev)
 {
diff --git a/include/linux/ste_modem_shm.h b/include/linux/ste_modem_shm.h
new file mode 100644 (file)
index 0000000..8444a4e
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2012
+ * Author: Sjur Brendeland / sjur.brandeland@stericsson.com
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#ifndef __INC_MODEM_DEV_H
+#define __INC_MODEM_DEV_H
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+struct ste_modem_device;
+
+/**
+ * struct ste_modem_dev_cb - Callbacks for modem initiated events.
+ * @kick: Called when the modem kicks the host.
+ *
+ * This structure contains callbacks for actions triggered by the modem.
+ */
+struct ste_modem_dev_cb {
+       void (*kick)(struct ste_modem_device *mdev, int notify_id);
+};
+
+/**
+ * struct ste_modem_dev_ops - Functions to control modem and modem interface.
+ *
+ * @power:     Main power switch, used for cold-start or complete power off.
+ * @kick:      Kick the modem.
+ * @kick_subscribe: Subscribe for notifications from the modem.
+ * @setup:     Provide callback functions to modem device.
+ *
+ * This structure contains functions used by the ste remoteproc driver
+ * to manage the modem.
+ */
+struct ste_modem_dev_ops {
+       int (*power)(struct ste_modem_device *mdev, bool on);
+       int (*kick)(struct ste_modem_device *mdev, int notify_id);
+       int (*kick_subscribe)(struct ste_modem_device *mdev, int notify_id);
+       int (*setup)(struct ste_modem_device *mdev,
+                    struct ste_modem_dev_cb *cfg);
+};
+
+/**
+ * struct ste_modem_device - represent the STE modem device
+ * @pdev: Reference to platform device
+ * @ops: Operations used to manage the modem.
+ * @drv_data: Driver private data.
+ */
+struct ste_modem_device {
+       struct platform_device pdev;
+       struct ste_modem_dev_ops ops;
+       void *drv_data;
+};
+
+#endif /*INC_MODEM_DEV_H*/