Merge tag 'omap-for-v3.7-rc1/fixes-pm-signed' of git://git.kernel.org/pub/scm/linux...
[firefly-linux-kernel-4.4.55.git] / drivers / remoteproc / remoteproc_core.c
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();