drm_hwcomposer: Implement eventControl with vsync worker

Adds a new worker to handle events requested by eventControl.

Signed-off-by: Sean Paul <seanpaul@chromium.org>
Change-Id: I6d63bb43b5f112e2efb0c426e7fd59910fc98953
diff --git a/hwcomposer.cpp b/hwcomposer.cpp
index ea60c36..dac7ed4 100644
--- a/hwcomposer.cpp
+++ b/hwcomposer.cpp
@@ -66,6 +66,7 @@
 
 	int active_config;
 	uint32_t active_crtc;
+	int active_pipe;
 
 	struct hwc_worker set_worker;
 
@@ -74,6 +75,9 @@
 
 	int timeline_fd;
 	unsigned timeline_next;
+
+	struct hwc_worker vsync_worker;
+	bool enable_vsync_events;
 };
 
 struct hwc_context_t {
@@ -466,13 +470,138 @@
 	return ret;
 }
 
-static int hwc_event_control(struct hwc_composer_device_1 */* dev */,
-			int /* display */, int /* event */, int /* enabled */)
+static int hwc_wait_for_vsync(struct hwc_drm_display *hd)
 {
+	drmVBlank vblank;
+	int ret;
+	uint32_t high_crtc;
+	int64_t timestamp;
+
+	if (hd->active_pipe == -1)
+		return -EINVAL;
+
+	memset(&vblank, 0, sizeof(vblank));
+
+	high_crtc = (hd->active_pipe << DRM_VBLANK_HIGH_CRTC_SHIFT);
+	vblank.request.type = (drmVBlankSeqType)(DRM_VBLANK_RELATIVE |
+		(high_crtc & DRM_VBLANK_HIGH_CRTC_MASK));
+	vblank.request.sequence = 1;
+
+	ret = drmWaitVBlank(hd->ctx->fd, &vblank);
+	if (ret) {
+		ALOGE("Failed to wait for vblank %d", ret);
+		return ret;
+	}
+
+	if (hd->ctx->procs->vsync) {
+		timestamp = vblank.reply.tval_sec * 1000 * 1000 * 1000 +
+			vblank.reply.tval_usec * 1000;
+		hd->ctx->procs->vsync(hd->ctx->procs, hd->display, timestamp);
+	}
+
+	return 0;
+}
+
+static void *hwc_vsync_worker(void *arg)
+{
+	struct hwc_drm_display *hd = (struct hwc_drm_display *)arg;
+	struct hwc_worker *w = &hd->vsync_worker;
 	int ret;
 
-	/* TODO */
+	setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
+
+	do {
+		ret = pthread_mutex_lock(&w->lock);
+		if (ret) {
+			ALOGE("Failed to lock vsync lock %d", ret);
+			return NULL;
+		}
+
+		if (hd->active_pipe == -1) {
+			ALOGE("Pipe is no longer active, disable events");
+			hd->enable_vsync_events = false;
+		}
+
+		if (!hd->enable_vsync_events) {
+			ret = pthread_cond_wait(&w->cond, &w->lock);
+			if (ret) {
+				ALOGE("Failed to wait on vsync cond %d", ret);
+				break;
+			}
+		}
+
+		if (w->exit)
+			break;
+
+		ret = pthread_mutex_unlock(&w->lock);
+		if (ret) {
+			ALOGE("Failed to unlock vsync lock %d", ret);
+			return NULL;
+		}
+
+		if (!hd->enable_vsync_events)
+			continue;
+
+		ret = hwc_wait_for_vsync(hd);
+		if (ret)
+			ALOGE("Failed to wait for vsync %d", ret);
+
+	} while (true);
+
+out:
+	ret = pthread_mutex_unlock(&hd->set_worker.lock);
+	if (ret)
+		ALOGE("Failed to unlock set lock while exiting %d", ret);
+
+	return NULL;
+}
+
+static int hwc_event_control(struct hwc_composer_device_1* dev, int display,
+			int event, int enabled)
+{
+	struct hwc_context_t *ctx = (struct hwc_context_t *)&dev->common;
+	struct hwc_drm_display *hd = NULL;
+	int ret;
+
+	ret = hwc_get_drm_display(ctx, display, &hd);
+	if (ret)
+		return ret;
+
+	if (event != HWC_EVENT_VSYNC || (enabled != 0 && enabled != 1))
+		return -EINVAL;
+
+	if (hd->active_pipe == -1) {
+		ALOGD("Can't service events for display %d, no pipe", display);
+		return -EINVAL;
+	}
+
+	ret = pthread_mutex_lock(&hd->vsync_worker.lock);
+	if (ret) {
+		ALOGE("Failed to lock vsync lock %d", ret);
+		return ret;
+	}
+
+	hd->enable_vsync_events = !!enabled;
+
+	ret = pthread_cond_signal(&hd->vsync_worker.cond);
+	if (ret) {
+		ALOGE("Failed to signal vsync thread %d", ret);
+		goto out;
+	}
+
+	ret = pthread_mutex_unlock(&hd->vsync_worker.lock);
+	if (ret) {
+		ALOGE("Failed to unlock vsync lock %d", ret);
+		return ret;
+	}
+
 	return 0;
+
+out:
+	if (pthread_mutex_unlock(&hd->vsync_worker.lock))
+		ALOGE("Failed to unlock vsync worker in error path");
+
+	return ret;
 }
 
 static int hwc_set_power_mode(struct hwc_composer_device_1* dev, int display,
@@ -835,6 +964,7 @@
 
 	/* We no longer have an active_crtc */
 	hd->active_crtc = 0;
+	hd->active_pipe = -1;
 
 	/* First, try to use the currently-connected encoder */
 	if (c->encoder_id) {
@@ -866,6 +996,21 @@
 	hd->active_crtc = crtc_id;
 	hd->active_config = index;
 
+	/* Find the pipe corresponding to the crtc_id */
+	for (i = 0; i < r->count_crtcs; i++) {
+		/* We've already tried this earlier */
+		if (r->crtcs[i] == crtc_id) {
+			hd->active_pipe = i;
+			break;
+		}
+	}
+	/* This should never happen... hehehe */
+	if (hd->active_pipe == -1) {
+		ALOGE("Active crtc was not found in resources!!");
+		ret = -ENODEV;
+		goto out;
+	}
+
 	/* TODO: Once we have atomic, set the crtc timing info here */
 
 out:
@@ -909,6 +1054,9 @@
 
 	if (hwc_destroy_worker(&hd->set_worker))
 		ALOGE("Destroy set worker failed");
+
+	if (hwc_destroy_worker(&hd->vsync_worker))
+		ALOGE("Destroy vsync worker failed");
 }
 
 static int hwc_device_close(struct hw_device_t *dev)
@@ -976,6 +1124,7 @@
 	hd->ctx = ctx;
 	hd->display = display;
 	hd->active_config = -1;
+	hd->active_pipe = -1;
 	hd->connector_id = connector_id;
 
 	ret = sw_sync_timeline_create();
@@ -991,7 +1140,19 @@
 		return ret;
 	}
 
+	ret = hwc_initialize_worker(hd, &hd->vsync_worker, hwc_vsync_worker);
+	if (ret) {
+		ALOGE("Failed to create vsync worker %d", ret);
+		goto err;
+	}
+
 	return 0;
+
+err:
+	if (hwc_destroy_worker(&hd->set_worker))
+		ALOGE("Failed to destroy set worker");
+
+	return ret;
 }
 
 static int hwc_enumerate_displays(struct hwc_context_t *ctx)