msm: kgsl: Add timestamp events
Add support for triggering asynchronous events when a timestamp
expires. The infrastructure is generic enough to support different
sorts of events and callbacks. The first user of the event
infrastructure is a way to release a genlock locks on behalf of a
user space process.
Change-Id: Ic0dedbadfe67d98a5678453cbe0ac6996ba5c68f
Signed-off-by: Jordan Crouse <jcrouse@codeaurora.org>
diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c
index e39a86d..53b9e8a 100644
--- a/drivers/gpu/msm/kgsl.c
+++ b/drivers/gpu/msm/kgsl.c
@@ -20,6 +20,7 @@
#include <linux/android_pmem.h>
#include <linux/vmalloc.h>
#include <linux/pm_runtime.h>
+#include <linux/genlock.h>
#include <linux/ashmem.h>
#include <linux/major.h>
@@ -47,6 +48,63 @@
static struct ion_client *kgsl_ion_client;
+#ifdef CONFIG_GENLOCK
+
+/**
+ * kgsl_add_event - Add a new timstamp event for the KGSL device
+ * @device - KGSL device for the new event
+ * @ts - the timestamp to trigger the event on
+ * @cb - callback function to call when the timestamp expires
+ * @priv - private data for the specific event type
+ *
+ * @returns - 0 on success or error code on failure
+ */
+
+static int kgsl_add_event(struct kgsl_device *device, u32 ts,
+ void (*cb)(struct kgsl_device *, void *, u32), void *priv)
+{
+ struct kgsl_event *event;
+ struct list_head *n;
+ unsigned int cur = device->ftbl->readtimestamp(device,
+ KGSL_TIMESTAMP_RETIRED);
+
+ if (cb == NULL)
+ return -EINVAL;
+
+ /* Check to see if the requested timestamp has already fired */
+
+ if (timestamp_cmp(cur, ts) >= 0) {
+ cb(device, priv, cur);
+ return 0;
+ }
+
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+ if (event == NULL)
+ return -ENOMEM;
+
+ event->timestamp = ts;
+ event->priv = priv;
+ event->func = cb;
+
+ /* Add the event in order to the list */
+
+ for (n = device->events.next ; n != &device->events; n = n->next) {
+ struct kgsl_event *e =
+ list_entry(n, struct kgsl_event, list);
+
+ if (timestamp_cmp(e->timestamp, ts) > 0) {
+ list_add(&event->list, n->prev);
+ break;
+ }
+ }
+
+ if (n == &device->events)
+ list_add_tail(&event->list, &device->events);
+
+ return 0;
+}
+#endif
+
static inline struct kgsl_mem_entry *
kgsl_mem_entry_create(void)
{
@@ -205,6 +263,7 @@
struct kgsl_device *device = container_of(work, struct kgsl_device,
ts_expired_ws);
struct kgsl_mem_entry *entry, *entry_tmp;
+ struct kgsl_event *event, *event_tmp;
uint32_t ts_processed;
mutex_lock(&device->mutex);
@@ -222,6 +281,18 @@
kgsl_mem_entry_put(entry);
}
+ /* Process expired events */
+ list_for_each_entry_safe(event, event_tmp, &device->events, list) {
+ if (timestamp_cmp(ts_processed, event->timestamp) < 0)
+ break;
+
+ if (event->func)
+ event->func(device, event->priv, ts_processed);
+
+ list_del(&event->list);
+ kfree(event);
+ }
+
mutex_unlock(&device->mutex);
}
@@ -1676,6 +1747,114 @@
return result;
}
+#ifdef CONFIG_GENLOCK
+struct kgsl_genlock_event_priv {
+ struct genlock_handle *handle;
+ struct genlock *lock;
+};
+
+/**
+ * kgsl_genlock_event_cb - Event callback for a genlock timestamp event
+ * @device - The KGSL device that expired the timestamp
+ * @priv - private data for the event
+ * @timestamp - the timestamp that triggered the event
+ *
+ * Release a genlock lock following the expiration of a timestamp
+ */
+
+static void kgsl_genlock_event_cb(struct kgsl_device *device,
+ void *priv, u32 timestamp)
+{
+ struct kgsl_genlock_event_priv *ev = priv;
+ int ret;
+
+ ret = genlock_lock(ev->handle, GENLOCK_UNLOCK, 0, 0);
+ if (ret)
+ KGSL_CORE_ERR("Error while unlocking genlock: %d\n", ret);
+
+ genlock_put_handle(ev->handle);
+
+ kfree(ev);
+}
+
+/**
+ * kgsl_add_genlock-event - Create a new genlock event
+ * @device - KGSL device to create the event on
+ * @timestamp - Timestamp to trigger the event
+ * @data - User space buffer containing struct kgsl_genlock_event_priv
+ * @len - length of the userspace buffer
+ * @returns 0 on success or error code on error
+ *
+ * Attack to a genlock handle and register an event to release the
+ * genlock lock when the timestamp expires
+ */
+
+static int kgsl_add_genlock_event(struct kgsl_device *device,
+ u32 timestamp, void __user *data, int len)
+{
+ struct kgsl_genlock_event_priv *event;
+ struct kgsl_timestamp_event_genlock priv;
+ int ret;
+
+ if (len != sizeof(priv))
+ return -EINVAL;
+
+ if (copy_from_user(&priv, data, sizeof(priv)))
+ return -EFAULT;
+
+ event = kzalloc(sizeof(*event), GFP_KERNEL);
+
+ if (event == NULL)
+ return -ENOMEM;
+
+ event->handle = genlock_get_handle_fd(priv.handle);
+
+ if (IS_ERR(event->handle)) {
+ int ret = PTR_ERR(event->handle);
+ kfree(event);
+ return ret;
+ }
+
+ ret = kgsl_add_event(device, timestamp, kgsl_genlock_event_cb, event);
+ if (ret)
+ kfree(event);
+
+ return ret;
+}
+#else
+static long kgsl_add_genlock_event(struct kgsl_device *device,
+ u32 timestamp, void __user *data, int len)
+{
+ return -EINVAL;
+}
+#endif
+
+/**
+ * kgsl_ioctl_timestamp_event - Register a new timestamp event from userspace
+ * @dev_priv - pointer to the private device structure
+ * @cmd - the ioctl cmd passed from kgsl_ioctl
+ * @data - the user data buffer from kgsl_ioctl
+ * @returns 0 on success or error code on failure
+ */
+
+static long kgsl_ioctl_timestamp_event(struct kgsl_device_private *dev_priv,
+ unsigned int cmd, void *data)
+{
+ struct kgsl_timestamp_event *param = data;
+ int ret;
+
+ switch (param->type) {
+ case KGSL_TIMESTAMP_EVENT_GENLOCK:
+ ret = kgsl_add_genlock_event(dev_priv->device,
+ param->timestamp, param->priv, param->len);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
typedef long (*kgsl_ioctl_func_t)(struct kgsl_device_private *,
unsigned int, void *);
@@ -1717,6 +1896,8 @@
kgsl_ioctl_cff_syncmem, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_USER_EVENT,
kgsl_ioctl_cff_user_event, 0),
+ KGSL_IOCTL_FUNC(IOCTL_KGSL_TIMESTAMP_EVENT,
+ kgsl_ioctl_timestamp_event, 0),
};
static long kgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
@@ -2021,6 +2202,7 @@
INIT_WORK(&device->ts_expired_ws, kgsl_timestamp_expired);
INIT_LIST_HEAD(&device->memqueue);
+ INIT_LIST_HEAD(&device->events);
ret = kgsl_mmu_init(device);
if (ret != 0)