/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <pthread.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <hardware/hwcomposer.h>
#include <hardware/hwcomposer_defs.h>
#include <log/log.h>
#include "common/vsoc/lib/screen_region_view.h"
#include "guest/hals/gralloc/gralloc_vsoc_priv.h"
// This file contains just a skeleton hwcomposer, the first step in the
// multisided vsoc hwcomposer for cuttlefish.
using vsoc::screen::ScreenRegionView;
// TODO(jemoreira): ScreenRegionView may become the HWC region
namespace {
// Ensures that the layer does not include any inconsistencies
int SanityCheckLayer(const hwc_layer_1& layer) {
// Check displayFrame
if (layer.displayFrame.left > layer.displayFrame.right ||
layer.displayFrame.top > layer.displayFrame.bottom) {
ALOGE(
"%s: Malformed rectangle (displayFrame): [left = %d, right = %d, top = "
"%d, bottom = %d]",
__FUNCTION__, layer.displayFrame.left, layer.displayFrame.right,
layer.displayFrame.top, layer.displayFrame.bottom);
return -EINVAL;
}
// Check sourceCrop
if (layer.sourceCrop.left > layer.sourceCrop.right ||
layer.sourceCrop.top > layer.sourceCrop.bottom) {
ALOGE(
"%s: Malformed rectangle (sourceCrop): [left = %d, right = %d, top = "
"%d, bottom = %d]",
__FUNCTION__, layer.sourceCrop.left, layer.sourceCrop.right,
layer.sourceCrop.top, layer.sourceCrop.bottom);
return -EINVAL;
}
const vsoc_buffer_handle_t* p_handle =
reinterpret_cast<const vsoc_buffer_handle_t*>(layer.handle);
if (!p_handle) {
ALOGE("Layer has a NULL buffer handle");
return -EINVAL;
}
if (layer.sourceCrop.left < 0 || layer.sourceCrop.top < 0 ||
layer.sourceCrop.right > p_handle->x_res ||
layer.sourceCrop.bottom > p_handle->y_res) {
ALOGE(
"%s: Invalid sourceCrop for buffer handle: sourceCrop = [left = %d, "
"right = %d, top = %d, bottom = %d], handle = [width = %d, height = "
"%d]",
__FUNCTION__, layer.sourceCrop.left, layer.sourceCrop.right,
layer.sourceCrop.top, layer.sourceCrop.bottom, p_handle->x_res,
p_handle->y_res);
return -EINVAL;
}
return 0;
}
struct vsoc_hwc_device {
hwc_composer_device_1_t base;
const hwc_procs_t* procs;
pthread_t vsync_thread;
int64_t vsync_base_timestamp;
int32_t vsync_period_ns;
uint32_t frame_num;
};
void* vsync_thread(void* arg) {
vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(arg);
setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
int64_t base_timestamp = pdev->vsync_base_timestamp;
int64_t last_logged = base_timestamp / 1e9;
int sent = 0;
int last_sent = 0;
static const int log_interval = 60;
void (*vsync_proc)(const struct hwc_procs*, int, int64_t) = nullptr;
bool log_no_procs = true, log_no_vsync = true;
while (true) {
struct timespec rt;
if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
strerror(errno));
}
int64_t timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
// Given now's timestamp calculate the time of the next vsync.
timestamp += pdev->vsync_period_ns -
(timestamp - base_timestamp) % pdev->vsync_period_ns;
rt.tv_sec = timestamp / 1e9;
rt.tv_nsec = timestamp % static_cast<int32_t>(1e9);
int err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &rt, NULL);
if (err == -1) {
ALOGE("error in vsync thread: %s", strerror(errno));
if (errno == EINTR) {
continue;
}
}
// The vsync thread is started on device open, it may run before the
// registerProcs callback has a chance to be called, so we need to make sure
// procs is not NULL before dereferencing it.
if (pdev && pdev->procs) {
vsync_proc = pdev->procs->vsync;
} else if (log_no_procs) {
log_no_procs = false;
ALOGI("procs is not set yet, unable to deliver vsync event");
}
if (vsync_proc) {
vsync_proc(const_cast<hwc_procs_t*>(pdev->procs), 0, timestamp);
++sent;
} else if (log_no_vsync) {
log_no_vsync = false;
ALOGE("vsync callback is null (but procs was already set)");
}
if (rt.tv_sec - last_logged > log_interval) {
ALOGI("Sent %d syncs in %ds", sent - last_sent, log_interval);
last_logged = rt.tv_sec;
last_sent = sent;
}
}
return NULL;
}
int hwc_prepare(struct hwc_composer_device_1* /*dev*/, size_t numDisplays,
hwc_display_contents_1_t** displays) {
if (!numDisplays || !displays) return 0;
hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
if (!list) return 0;
for (size_t i = 0; i < list->numHwLayers; i++) {
if (list->hwLayers[i].compositionType == HWC_FRAMEBUFFER_TARGET) {
continue;
}
list->hwLayers[i].compositionType = HWC_FRAMEBUFFER;
}
return 0;
}
int hwc_set(struct hwc_composer_device_1* dev, size_t numDisplays,
hwc_display_contents_1_t** displays) {
if (!numDisplays || !displays) return 0;
hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
if (!list) return 0;
if (!dev) {
ALOGE("%s: dev is NULL", __FUNCTION__);
return -EINVAL;
}
for (size_t i = 0; i < list->numHwLayers; i++) {
if (vsoc_buffer_handle_t::validate(list->hwLayers[i].handle)) {
return -EINVAL;
}
if (list->hwLayers[i].compositionType == HWC_FRAMEBUFFER_TARGET) {
if (SanityCheckLayer(list->hwLayers[i])) {
ALOGW("Skipping layer %zu due to failed sanity check", i);
continue;
}
const vsoc_buffer_handle_t* fb_handle =
reinterpret_cast<const vsoc_buffer_handle_t*>(
list->hwLayers[i].handle);
ScreenRegionView::GetInstance()->BroadcastNewFrame(
fb_handle->offset);
break;
}
}
return 0;
}
int hwc_eventControl(struct hwc_composer_device_1* /*dev*/, int disp, int event,
int /*enabled*/) {
if (event == HWC_EVENT_VSYNC || disp != HWC_DISPLAY_PRIMARY) {
return 0;
}
return -EINVAL;
}
int hwc_blank(struct hwc_composer_device_1* /*dev*/, int disp, int /*blank*/) {
if (disp != HWC_DISPLAY_PRIMARY) {
return -EINVAL;
}
return 0;
}
int hwc_query(struct hwc_composer_device_1* dev, int what, int* value) {
vsoc_hwc_device* pdev = reinterpret_cast<vsoc_hwc_device*>(dev);
switch (what) {
case HWC_BACKGROUND_LAYER_SUPPORTED:
// we don't support the background layer
*value = 0;
break;
case HWC_VSYNC_PERIOD:
*value = pdev->vsync_period_ns;
break;
case HWC_DISPLAY_TYPES_SUPPORTED:
// We only support the primary display
*value = HWC_DISPLAY_PRIMARY_BIT;
break;
default:
// unsupported query
ALOGE("%s badness unsupported query what=%d", __FUNCTION__, what);
return -EINVAL;
}
return 0;
}
void hwc_registerProcs(struct hwc_composer_device_1* dev,
hwc_procs_t const* procs) {
reinterpret_cast<vsoc_hwc_device*>(dev)->procs = procs;
}
void hwc_dump(struct hwc_composer_device_1* /*dev*/, char* /*buff*/,
int /*buff_len*/) {}
int hwc_getDisplayConfigs(struct hwc_composer_device_1* /*dev*/, int disp,
uint32_t* configs, size_t* numConfigs) {
if (*numConfigs == 0) return 0;
if (disp == HWC_DISPLAY_PRIMARY) {
configs[0] = 0;
*numConfigs = 1;
return 0;
}
return -EINVAL;
}
int32_t vsoc_hwc_attribute(uint32_t attribute) {
auto screen_view = ScreenRegionView::GetInstance();
switch (attribute) {
case HWC_DISPLAY_VSYNC_PERIOD:
return 1000000000 / screen_view->refresh_rate_hz();
case HWC_DISPLAY_WIDTH:
return screen_view->x_res();
case HWC_DISPLAY_HEIGHT:
return screen_view->y_res();
case HWC_DISPLAY_DPI_X:
case HWC_DISPLAY_DPI_Y:
// The number of pixels per thousand inches
return screen_view->dpi() * 1000;
case HWC_DISPLAY_COLOR_TRANSFORM:
// TODO(jemoreira): Add the other color transformations
return HAL_COLOR_TRANSFORM_IDENTITY;
default:
ALOGE("unknown display attribute %u", attribute);
return -EINVAL;
}
}
int hwc_getDisplayAttributes(struct hwc_composer_device_1* /*dev*/, int disp,
uint32_t /*config*/, const uint32_t* attributes,
int32_t* values) {
if (disp != HWC_DISPLAY_PRIMARY) {
ALOGE("Unknown display type %u", disp);
return -EINVAL;
}
for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++) {
values[i] = vsoc_hwc_attribute(attributes[i]);
}
return 0;
}
int hwc_close(hw_device_t* device) {
vsoc_hwc_device* dev = reinterpret_cast<vsoc_hwc_device*>(device);
pthread_kill(dev->vsync_thread, SIGTERM);
pthread_join(dev->vsync_thread, NULL);
delete dev;
return 0;
}
int hwc_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device) {
ALOGI("Opening vsoc hwcomposer device: %s", __FUNCTION__);
if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
ALOGE("%s called with bad name %s", __FUNCTION__, name);
return -EINVAL;
}
vsoc_hwc_device* dev = new vsoc_hwc_device();
if (!dev) {
ALOGE("%s failed to allocate dev", __FUNCTION__);
return -ENOMEM;
}
memset(dev, 0, sizeof(*dev));
int refreshRate = 60;
dev->vsync_period_ns = 1000000000 / refreshRate;
struct timespec rt;
if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
ALOGE("%s:%d error in clock_gettime: %s", __FILE__, __LINE__,
strerror(errno));
}
dev->vsync_base_timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
dev->base.common.tag = HARDWARE_DEVICE_TAG;
dev->base.common.version = HWC_DEVICE_API_VERSION_1_1;
dev->base.common.module = const_cast<hw_module_t*>(module);
dev->base.common.close = hwc_close;
dev->base.prepare = hwc_prepare;
dev->base.set = hwc_set;
dev->base.query = hwc_query;
dev->base.registerProcs = hwc_registerProcs;
dev->base.dump = hwc_dump;
dev->base.blank = hwc_blank;
dev->base.eventControl = hwc_eventControl;
dev->base.getDisplayConfigs = hwc_getDisplayConfigs;
dev->base.getDisplayAttributes = hwc_getDisplayAttributes;
if (!ScreenRegionView::GetInstance()) {
ALOGE("Unable to open screen region (%s)", __FUNCTION__);
delete dev;
return -1;
}
int ret = pthread_create(&dev->vsync_thread, NULL, vsync_thread, dev);
if (ret) {
ALOGE("failed to start vsync thread: %s", strerror(ret));
delete dev;
} else {
*device = &dev->base.common;
}
return -ret;
}
struct hw_module_methods_t hwc_module_methods = {hwc_open};
} // namespace
hwc_module_t HAL_MODULE_INFO_SYM = {{HARDWARE_MODULE_TAG,
HWC_MODULE_API_VERSION_0_1,
HARDWARE_HAL_API_VERSION,
HWC_HARDWARE_MODULE_ID,
"Cuttlefish hwcomposer module",
"Google",
&hwc_module_methods,
NULL,
{0}}};