summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Anholt <eric@anholt.net>2014-10-16 10:48:30 +0100
committerEric Anholt <eric@anholt.net>2015-06-04 14:15:24 -0700
commit631c7adff119fc2fedb09e64a9e2057501648d59 (patch)
treea00001277a58c59be7bf829b986d7edde7bf3bbd
parent3c5db5e5e5f77d6a589b3a11f18d6a5e7744fec7 (diff)
downloadlinux-631c7adff119fc2fedb09e64a9e2057501648d59.tar.gz
drm/vc4: Use interrupts for binner overflow memory allocation handling.
This avoids some of the checks in the spinloop waiting for frame done, and avoids the race I had documented, as far as I know. Note that this reportedly requires "mask_gpu_interrupt0=0x400" in config.txt to keep the VPU firmware from trying to do things based on our interrupts. v2: Rebase on 3.19 DRM core APIs. v3: Rebase on the KMS work. Signed-off-by: Eric Anholt <eric@anholt.net>
-rw-r--r--drivers/gpu/drm/vc4/Makefile1
-rw-r--r--drivers/gpu/drm/vc4/vc4_drv.c8
-rw-r--r--drivers/gpu/drm/vc4/vc4_drv.h20
-rw-r--r--drivers/gpu/drm/vc4/vc4_gem.c79
-rw-r--r--drivers/gpu/drm/vc4/vc4_irq.c129
-rw-r--r--drivers/gpu/drm/vc4/vc4_v3d.c8
6 files changed, 174 insertions, 71 deletions
diff --git a/drivers/gpu/drm/vc4/Makefile b/drivers/gpu/drm/vc4/Makefile
index 09dad06a75b9..0abf23d62f9e 100644
--- a/drivers/gpu/drm/vc4/Makefile
+++ b/drivers/gpu/drm/vc4/Makefile
@@ -11,6 +11,7 @@ vc4-y := \
vc4_gem.o \
vc4_hdmi.o \
vc4_hvs.o \
+ vc4_irq.o \
vc4_plane.o \
vc4_v3d.o \
vc4_validate.o \
diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c
index ba1145a98e38..433be8c09efa 100644
--- a/drivers/gpu/drm/vc4/vc4_drv.c
+++ b/drivers/gpu/drm/vc4/vc4_drv.c
@@ -58,6 +58,8 @@ vc4_drm_load(struct drm_device *dev, unsigned long flags)
if (!vc4)
return -ENOMEM;
+ INIT_LIST_HEAD(&vc4->overflow_list);
+
vc4->firmware_node = of_parse_phandle(dev->dev->of_node, "firmware", 0);
if (!vc4->firmware_node) {
DRM_ERROR("Failed to parse firmware node.\n");
@@ -114,11 +116,17 @@ static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
static struct drm_driver vc4_drm_driver = {
.driver_features = (DRIVER_MODESET |
DRIVER_GEM |
+ DRIVER_HAVE_IRQ |
DRIVER_PRIME),
.load = vc4_drm_load,
.unload = vc4_drm_unload,
.set_busid = drm_platform_set_busid,
+ .irq_handler = vc4_irq,
+ .irq_preinstall = vc4_irq_preinstall,
+ .irq_postinstall = vc4_irq_postinstall,
+ .irq_uninstall = vc4_irq_uninstall,
+
.enable_vblank = vc4_enable_vblank,
.disable_vblank = vc4_disable_vblank,
.get_vblank_counter = drm_vblank_count,
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h
index bf0d749a312f..8b745c3bac40 100644
--- a/drivers/gpu/drm/vc4/vc4_drv.h
+++ b/drivers/gpu/drm/vc4/vc4_drv.h
@@ -18,6 +18,13 @@ struct vc4_dev {
struct vc4_hvs *hvs;
struct vc4_crtc *crtc[3];
struct vc4_v3d *v3d;
+
+ /* List of struct vc4_list_bo_entry allocated to accomodate
+ * binner overflow. These will be freed when the exec is
+ * done.
+ */
+ struct list_head overflow_list;
+ struct work_struct overflow_mem_work;
};
static inline struct vc4_dev *
@@ -122,12 +129,6 @@ struct exec_info {
*/
struct drm_gem_cma_object *exec_bo;
- /* List of struct vc4_list_bo_entry allocated to accomodate
- * binner overflow. These will be freed when the exec is
- * done.
- */
- struct list_head overflow_list;
-
/**
* This tracks the per-shader-record state (packet 64) that
* determines the length of the shader record and the offset
@@ -283,6 +284,13 @@ struct drm_connector *vc4_hdmi_connector_init(struct drm_device *dev,
struct drm_encoder *encoder);
int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused);
+/* vc4_irq.c */
+irqreturn_t vc4_irq(int irq, void *arg);
+void vc4_irq_preinstall(struct drm_device *dev);
+int vc4_irq_postinstall(struct drm_device *dev);
+void vc4_irq_uninstall(struct drm_device *dev);
+void vc4_irq_reset(struct drm_device *dev);
+
/* vc4_hvs.c */
void vc4_hvs_register(void);
void vc4_hvs_unregister(void);
diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c
index 9dc661ee3798..3c38e41bd41f 100644
--- a/drivers/gpu/drm/vc4/vc4_gem.c
+++ b/drivers/gpu/drm/vc4/vc4_gem.c
@@ -38,6 +38,8 @@ vc4_reset(struct drm_device *dev)
DRM_INFO("Resetting GPU.\n");
vc4_v3d_set_power(vc4, false);
vc4_v3d_set_power(vc4, true);
+
+ vc4_irq_reset(dev);
}
static void
@@ -69,77 +71,16 @@ thread_stopped(struct drm_device *dev, uint32_t thread)
}
static int
-try_adding_overflow_memory(struct drm_device *dev, struct exec_info *exec)
-{
- struct vc4_dev *vc4 = to_vc4_dev(dev);
- struct vc4_bo_list_entry *entry = kmalloc(sizeof(*entry), GFP_KERNEL);
- int ret;
-
- if (!entry)
- return -ENOMEM;
-
- entry->bo = drm_gem_cma_create(dev, 256 * 1024);
- if (IS_ERR(entry->bo)) {
- int ret = PTR_ERR(entry->bo);
- DRM_ERROR("Couldn't allocate binner overflow mem\n");
- kfree(entry);
- return ret;
- }
-
- list_add_tail(&entry->head, &exec->overflow_list);
-
- V3D_WRITE(V3D_BPOA, entry->bo->paddr);
- V3D_WRITE(V3D_BPOS, entry->bo->base.size);
-
- /* Wait for the hardware to ack our supplied memory before
- * continuing. There's an ugly race here where if the
- * hardware gets the request and continues on to overflow
- * again before we read the reg, we'll time out.
- */
- ret = wait_for((V3D_READ(V3D_PCS) & V3D_BMOOM) == 0, 1000);
- if (ret) {
- DRM_ERROR("Timed out waiting for hardware to ack "
- "overflow memory\n");
- return ret;
- }
-
- return 0;
-}
-
-static bool
-vc4_job_finished(struct drm_device *dev, struct exec_info *exec)
-{
- struct vc4_dev *vc4 = to_vc4_dev(dev);
- bool stopped = thread_stopped(dev, 1);
-
- /* If the thread is merely paused waiting for overflow memory,
- * hand some to it so we can make progress.
- */
- if (V3D_READ(V3D_PCS) & V3D_BMOOM) {
- int ret = try_adding_overflow_memory(dev, exec);
- return ret != 0;
- }
-
- return stopped;
-}
-
-static int
wait_for_job(struct drm_device *dev, struct exec_info *exec)
{
- struct vc4_dev *vc4 = to_vc4_dev(dev);
int ret;
- ret = wait_for(vc4_job_finished(dev, exec), 1000);
+ ret = wait_for(thread_stopped(dev, 1), 1000);
if (ret) {
DRM_ERROR("timeout waiting for render thread idle\n");
return ret;
}
- if (V3D_READ(V3D_PCS) & V3D_BMOOM) {
- DRM_ERROR("binner oom and stopped.\n");
- return -EINVAL;
- }
-
return 0;
}
@@ -383,13 +324,13 @@ int
vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
struct exec_info exec;
int ret;
int i;
memset(&exec, 0, sizeof(exec));
exec.args = data;
- INIT_LIST_HEAD(&exec.overflow_list);
mutex_lock(&dev->struct_mutex);
@@ -414,10 +355,18 @@ fail:
kfree(exec.bo);
}
- while (!list_empty(&exec.overflow_list)) {
+ /* Release all but the very last overflow list entry (which is
+ * still sitting in the BPOS/BPOA registers for use by the
+ * next job).
+ */
+ while (true) {
struct vc4_bo_list_entry *entry =
- list_first_entry(&exec.overflow_list,
+ list_first_entry(&vc4->overflow_list,
struct vc4_bo_list_entry, head);
+
+ if (entry->head.next == &vc4->overflow_list)
+ break;
+
drm_gem_object_unreference(&entry->bo->base);
list_del(&entry->head);
kfree(entry);
diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c
new file mode 100644
index 000000000000..dfc302606762
--- /dev/null
+++ b/drivers/gpu/drm/vc4/vc4_irq.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2014 Broadcom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "vc4_drv.h"
+#include "vc4_regs.h"
+
+#define V3D_DRIVER_IRQS V3D_INT_OUTOMEM
+
+static void
+vc4_overflow_mem_work(struct work_struct *work)
+{
+ struct vc4_dev *vc4 =
+ container_of(work, struct vc4_dev, overflow_mem_work);
+ struct drm_device *dev = vc4->dev;
+ struct vc4_bo_list_entry *entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+
+ if (!entry) {
+ DRM_ERROR("Couldn't allocate binner overflow mem record\n");
+ return;
+ }
+
+ entry->bo = drm_gem_cma_create(dev, 256 * 1024);
+ if (IS_ERR(entry->bo)) {
+ DRM_ERROR("Couldn't allocate binner overflow mem\n");
+ kfree(entry);
+ return;
+ }
+
+ list_add_tail(&entry->head, &vc4->overflow_list);
+
+ V3D_WRITE(V3D_BPOA, entry->bo->paddr);
+ V3D_WRITE(V3D_BPOS, entry->bo->base.size);
+ V3D_WRITE(V3D_INTDIS, 0);
+ V3D_WRITE(V3D_INTCTL, V3D_INT_OUTOMEM);
+}
+
+irqreturn_t
+vc4_irq(int irq, void *arg)
+{
+ struct drm_device *dev = arg;
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+ uint32_t intctl;
+
+ barrier();
+ intctl = V3D_READ(V3D_INTCTL);
+ V3D_WRITE(V3D_INTCTL, intctl);
+
+ if (intctl & V3D_INT_OUTOMEM) {
+ V3D_WRITE(V3D_INTDIS, V3D_INT_OUTOMEM);
+ schedule_work(&vc4->overflow_mem_work);
+ }
+
+ return intctl ? IRQ_HANDLED : IRQ_NONE;
+}
+
+void
+vc4_irq_preinstall(struct drm_device *dev)
+{
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+ INIT_WORK(&vc4->overflow_mem_work, vc4_overflow_mem_work);
+
+ /* Clear any pending interrupts someone might have left around
+ * for us.
+ */
+ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
+}
+
+int
+vc4_irq_postinstall(struct drm_device *dev)
+{
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+ /* Enable both the bin and render done interrupts, as well as
+ * out of memory. Eventually, we'll have the bin use internal
+ * semaphores with render to sync between the two, but for now
+ * we're driving that from the ARM.
+ */
+ V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS);
+
+ /* No interrupts disabled. */
+ V3D_WRITE(V3D_INTDIS, 0);
+
+ return 0;
+}
+
+void
+vc4_irq_uninstall(struct drm_device *dev)
+{
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+ cancel_work_sync(&vc4->overflow_mem_work);
+
+ V3D_WRITE(V3D_INTENA, 0);
+ V3D_WRITE(V3D_INTDIS, 0);
+
+ /* Clear any pending interrupts we might have left. */
+ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
+}
+
+/** Reinitializes interrupt registers when a GPU reset is performed. */
+void vc4_irq_reset(struct drm_device *dev)
+{
+ struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+ V3D_WRITE(V3D_INTCTL, V3D_DRIVER_IRQS);
+ V3D_WRITE(V3D_INTDIS, 0);
+ V3D_WRITE(V3D_INTENA, V3D_DRIVER_IRQS);
+}
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c
index 5a18cf41558a..9321fa2a2f23 100644
--- a/drivers/gpu/drm/vc4/vc4_v3d.c
+++ b/drivers/gpu/drm/vc4/vc4_v3d.c
@@ -202,6 +202,12 @@ static int vc4_v3d_bind(struct device *dev, struct device *master, void *data)
vc4_v3d_init_hw(drm);
+ ret = drm_irq_install(drm, platform_get_irq(pdev, 0));
+ if (ret) {
+ DRM_ERROR("Failed to install IRQ handler\n");
+ return ret;
+ }
+
return 0;
}
@@ -211,6 +217,8 @@ static void vc4_v3d_unbind(struct device *dev, struct device *master,
struct drm_device *drm = dev_get_drvdata(master);
struct vc4_dev *vc4 = to_vc4_dev(drm);
+ drm_irq_uninstall(drm);
+
vc4->v3d = NULL;
}