kmscon/src/uterm_video_drm.c
David Herrmann a13797ac76 uterm_video: fix drm debug messages
Print more verbose messages on DRM errors.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
2012-03-24 14:15:53 +01:00

893 lines
20 KiB
C

/*
* uterm - Linux User-Space Terminal
*
* Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
*
* 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 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.
*/
/*
* DRM Video backend
*/
#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <errno.h>
#include <fcntl.h>
#include <gbm.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <inttypes.h>
#include <libudev.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xf86drm.h>
/* #include <xf86drmMode.h> TODO: missing header protection */
#include "eloop.h"
#include "log.h"
#include "uterm.h"
#include "uterm_internal.h"
#define LOG_SUBSYSTEM "video_drm"
static const char *mode_get_name(const struct uterm_mode *mode)
{
return mode->drm.info.name;
}
static unsigned int mode_get_width(const struct uterm_mode *mode)
{
return mode->drm.info.hdisplay;
}
static unsigned int mode_get_height(const struct uterm_mode *mode)
{
return mode->drm.info.vdisplay;
}
static int init_rb(struct uterm_display *disp, struct drm_rb *rb)
{
unsigned int stride, handle;
int ret;
struct uterm_video *video = disp->video;
rb->bo = gbm_bo_create(video->drm.gbm,
disp->current_mode->drm.info.hdisplay,
disp->current_mode->drm.info.vdisplay,
GBM_BO_FORMAT_XRGB8888,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!rb->bo) {
log_err("cannot create gbm-bo");
return -EFAULT;
}
stride = gbm_bo_get_pitch(rb->bo);
handle = gbm_bo_get_handle(rb->bo).u32;
/* TODO: how can we choose 24/32 dynamically? */
ret = drmModeAddFB(video->drm.fd,
disp->current_mode->drm.info.hdisplay,
disp->current_mode->drm.info.vdisplay,
24, 32, stride, handle, &rb->fb);
if (ret) {
log_err("cannot add drm-fb");
ret = -EFAULT;
goto err_gbm;
}
rb->image = eglCreateImageKHR(video->drm.disp, NULL,
EGL_NATIVE_PIXMAP_KHR, rb->bo, NULL);
if (!rb->image) {
log_err("cannot create egl image");
ret = -EFAULT;
goto err_fb;
}
glGenRenderbuffers(1, &rb->rb);
glBindRenderbuffer(GL_RENDERBUFFER, rb->rb);
glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, rb->image);
return 0;
err_fb:
drmModeRmFB(video->drm.fd, rb->fb);
err_gbm:
gbm_bo_destroy(rb->bo);
return ret;
}
static void destroy_rb(struct uterm_display *disp, struct drm_rb *rb)
{
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glDeleteRenderbuffers(1, &rb->rb);
eglDestroyImageKHR(disp->video->drm.disp, rb->image);
drmModeRmFB(disp->video->drm.fd, rb->fb);
gbm_bo_destroy(rb->bo);
}
static int find_crtc(struct uterm_video *video, drmModeRes *res,
drmModeEncoder *enc)
{
int i, crtc;
struct uterm_display *iter;
for (i = 0; i < res->count_crtcs; ++i) {
if (enc->possible_crtcs & (1 << i)) {
crtc = res->crtcs[i];
for (iter = video->displays; iter; iter = iter->next) {
if (iter->drm.crtc_id == crtc)
break;
}
if (!iter)
return crtc;
}
}
return -1;
}
static int display_activate(struct uterm_display *disp, struct uterm_mode *mode)
{
struct uterm_video *video = disp->video;
int ret, crtc, i;
drmModeRes *res;
drmModeConnector *conn;
drmModeEncoder *enc;
if (!video || !video_is_awake(video) || !mode)
return -EINVAL;
if (display_is_online(disp))
return 0;
log_info("activating display %p to %ux%u", disp,
mode->drm.info.hdisplay, mode->drm.info.vdisplay);
res = drmModeGetResources(video->drm.fd);
if (!res) {
log_err("cannot get resources for display %p", disp);
return -EFAULT;
}
conn = drmModeGetConnector(video->drm.fd, disp->drm.conn_id);
if (!conn) {
log_err("cannot get connector for display %p", disp);
drmModeFreeResources(res);
return -EFAULT;
}
crtc = -1;
for (i = 0; i < conn->count_encoders; ++i) {
enc = drmModeGetEncoder(video->drm.fd, conn->encoders[i]);
if (!enc)
continue;
crtc = find_crtc(video, res, enc);
drmModeFreeEncoder(enc);
if (crtc >= 0)
break;
}
drmModeFreeConnector(conn);
drmModeFreeResources(res);
if (crtc < 0) {
log_warn("cannot find crtc for new display");
return -ENODEV;
}
disp->drm.crtc_id = crtc;
disp->drm.current_rb = 0;
disp->current_mode = mode;
disp->drm.saved_crtc = drmModeGetCrtc(video->drm.fd,
disp->drm.crtc_id);
ret = init_rb(disp, &disp->drm.rb[0]);
if (ret)
goto err_saved;
ret = init_rb(disp, &disp->drm.rb[1]);
if (ret)
goto err_rb;
glGenFramebuffers(1, &disp->drm.fb);
glBindFramebuffer(GL_FRAMEBUFFER, disp->drm.fb);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, disp->drm.rb[1].rb);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
GL_FRAMEBUFFER_COMPLETE) {
log_err("cannot create gl-framebuffer");
ret = -EFAULT;
goto err_fb;
}
ret = drmModeSetCrtc(video->drm.fd, disp->drm.crtc_id,
disp->drm.rb[0].fb, 0, 0, &disp->drm.conn_id, 1,
&disp->current_mode->drm.info);
if (ret) {
log_err("cannot set drm-crtc");
ret = -EFAULT;
goto err_fb;
}
disp->flags |= DISPLAY_ONLINE;
return 0;
err_fb:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &disp->drm.fb);
destroy_rb(disp, &disp->drm.rb[1]);
err_rb:
destroy_rb(disp, &disp->drm.rb[0]);
err_saved:
disp->current_mode = NULL;
if (disp->drm.saved_crtc) {
drmModeFreeCrtc(disp->drm.saved_crtc);
disp->drm.saved_crtc = NULL;
}
return ret;
}
static void display_deactivate(struct uterm_display *disp)
{
if (!display_is_online(disp))
return;
if (disp->drm.saved_crtc) {
if (disp->video->flags & VIDEO_AWAKE) {
drmModeSetCrtc(disp->video->drm.fd,
disp->drm.saved_crtc->crtc_id,
disp->drm.saved_crtc->buffer_id,
disp->drm.saved_crtc->x,
disp->drm.saved_crtc->y,
&disp->drm.conn_id,
1,
&disp->drm.saved_crtc->mode);
}
drmModeFreeCrtc(disp->drm.saved_crtc);
disp->drm.saved_crtc = NULL;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &disp->drm.fb);
destroy_rb(disp, &disp->drm.rb[1]);
destroy_rb(disp, &disp->drm.rb[0]);
disp->current_mode = NULL;
disp->flags &= ~(DISPLAY_ONLINE | DISPLAY_VSYNC);
log_info("deactivating display %p", disp);
}
static int display_set_dpms(struct uterm_display *disp, int state)
{
int i, ret, set;
drmModeConnector *conn;
drmModePropertyRes *prop;
if (!display_is_conn(disp) || !video_is_awake(disp->video))
return -EINVAL;
switch (state) {
case UTERM_DPMS_ON:
set = DRM_MODE_DPMS_ON;
break;
case UTERM_DPMS_STANDBY:
set = DRM_MODE_DPMS_STANDBY;
break;
case UTERM_DPMS_SUSPEND:
set = DRM_MODE_DPMS_SUSPEND;
break;
case UTERM_DPMS_OFF:
set = DRM_MODE_DPMS_OFF;
break;
default:
return -EINVAL;
}
log_info("setting DPMS of display %p to %s", disp,
uterm_dpms_to_name(state));
conn = drmModeGetConnector(disp->video->drm.fd, disp->drm.conn_id);
if (!conn) {
log_err("cannot get display connector");
return -EFAULT;
}
for (i = 0; i < conn->count_props; ++i) {
prop = drmModeGetProperty(disp->video->drm.fd, conn->props[i]);
if (!prop)
continue;
if (!strcmp(prop->name, "DPMS")) {
ret = drmModeConnectorSetProperty(disp->video->drm.fd,
disp->drm.conn_id, prop->prop_id, set);
if (ret) {
log_info("cannot set DPMS");
ret = -EFAULT;
}
drmModeFreeProperty(prop);
break;
}
drmModeFreeProperty(prop);
}
if (i == conn->count_props) {
ret = 0;
log_warn("display does not support DPMS");
state = UTERM_DPMS_UNKNOWN;
}
drmModeFreeConnector(conn);
disp->dpms = state;
return ret;
}
static int display_use(struct uterm_display *disp)
{
if (!display_is_online(disp))
return -EINVAL;
/* TODO: we need triple buffering as a VSYNC may still be pending */
glBindFramebuffer(GL_FRAMEBUFFER, disp->drm.fb);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, disp->drm.rb[disp->drm.current_rb ^ 1].rb);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) !=
GL_FRAMEBUFFER_COMPLETE) {
log_warn("cannot set gl-renderbuffer");
return -EFAULT;
}
return 0;
}
static int display_swap(struct uterm_display *disp)
{
int ret;
if (!display_is_conn(disp) || !video_is_awake(disp->video))
return -EINVAL;
if (disp->dpms != UTERM_DPMS_ON)
return -EINVAL;
/* TODO: is glFlush sufficient here? */
glFinish();
errno = 0;
disp->drm.current_rb ^= 1;
ret = drmModePageFlip(disp->video->drm.fd, disp->drm.crtc_id,
disp->drm.rb[disp->drm.current_rb].fb,
DRM_MODE_PAGE_FLIP_EVENT, disp);
if (ret) {
log_warn("page-flip failed %d %d", ret, errno);
return -EFAULT;
}
uterm_display_ref(disp);
disp->flags |= DISPLAY_VSYNC;
return 0;
}
static int get_dpms(struct uterm_display *disp, drmModeConnector *conn)
{
int i, ret;
drmModePropertyRes *prop;
for (i = 0; i < conn->count_props; ++i) {
prop = drmModeGetProperty(disp->video->drm.fd, conn->props[i]);
if (!prop)
continue;
if (!strcmp(prop->name, "DPMS")) {
switch (conn->prop_values[i]) {
case DRM_MODE_DPMS_ON:
ret = UTERM_DPMS_ON;
break;
case DRM_MODE_DPMS_STANDBY:
ret = UTERM_DPMS_STANDBY;
break;
case DRM_MODE_DPMS_SUSPEND:
ret = UTERM_DPMS_SUSPEND;
break;
case DRM_MODE_DPMS_OFF:
default:
ret = UTERM_DPMS_OFF;
}
drmModeFreeProperty(prop);
return ret;
}
drmModeFreeProperty(prop);
}
if (i == conn->count_props)
log_warn("display does not support DPMS");
return UTERM_DPMS_UNKNOWN;
}
static void bind_display(struct uterm_video *video, drmModeRes *res,
drmModeConnector *conn)
{
struct uterm_display *disp;
struct uterm_mode *mode;
int ret, i;
ret = display_new(&disp, &drm_display_ops);
if (ret)
return;
for (i = 0; i < conn->count_modes; ++i) {
ret = mode_new(&mode, &drm_mode_ops);
if (ret)
continue;
mode->drm.info = conn->modes[i];
mode->next = disp->modes;
disp->modes = mode;
/* TODO: more sophisticated default-mode selection */
if (!disp->default_mode)
disp->default_mode = mode;
}
if (!disp->modes) {
log_warn("no valid mode for display found");
uterm_display_unref(disp);
return;
}
disp->video = video;
disp->drm.conn_id = conn->connector_id;
disp->flags |= DISPLAY_AVAILABLE;
disp->next = video->displays;
video->displays = disp;
disp->dpms = get_dpms(disp, conn);
log_info("display %p DPMS is %s", disp,
uterm_dpms_to_name(disp->dpms));
}
static void unbind_display(struct uterm_display *disp)
{
if (!display_is_conn(disp))
return;
display_deactivate(disp);
disp->video = NULL;
disp->flags &= ~DISPLAY_AVAILABLE;
uterm_display_unref(disp);
}
static int get_id(struct udev_device *dev)
{
const char *name;
char *end;
int devnum;
name = udev_device_get_sysname(dev);
if (!name)
return -ENODEV;
if (strncmp(name, "card", 4) || !name[4])
return -ENODEV;
devnum = strtol(&name[4], &end, 10);
if (devnum < 0 || *end)
return -ENODEV;
return devnum;
}
static void page_flip_handler(int fd, unsigned int frame, unsigned int sec,
unsigned int usec, void *data)
{
struct uterm_display *disp = data;
disp->flags &= ~DISPLAY_VSYNC;
uterm_display_unref(disp);
}
static void event(struct ev_fd *fd, int mask, void *data)
{
struct uterm_video *video = data;
drmEventContext ev;
if (mask & (EV_HUP | EV_ERR)) {
log_err("error or hangup on DRM fd");
ev_eloop_rm_fd(video->drm.efd);
video->drm.efd = NULL;
return;
}
if (mask & EV_READABLE) {
memset(&ev, 0, sizeof(ev));
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = page_flip_handler;
drmHandleEvent(video->drm.fd, &ev);
}
}
static int init_device(struct uterm_video *video, struct udev_device *dev)
{
const char *node, *ext;
int ret;
EGLint major, minor;
EGLenum api;
static const EGLint ctx_att[] =
{ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
struct drm_video *drm = &video->drm;
log_info("probing %s", udev_device_get_sysname(dev));
node = udev_device_get_devnode(dev);
if (!node)
return -ENODEV;
drm->id = get_id(dev);
if (drm->id < 0) {
log_err("cannot get device id");
return -ENODEV;
}
drm->fd = open(node, O_RDWR | O_CLOEXEC);
if (drm->fd < 0) {
log_err("cannot open drm device %s (%d): %m", node, errno);
return -EFAULT;
}
drmDropMaster(drm->fd);
drm->gbm = gbm_create_device(drm->fd);
if (!drm->gbm) {
log_err("cannot create gbm device for %s (permission denied)",
node);
ret = -EFAULT;
goto err_close;
}
drm->disp = eglGetDisplay((EGLNativeDisplayType) drm->gbm);
if (!drm->disp) {
log_err("cannot retrieve egl display for %s", node);
ret = -EFAULT;
goto err_gbm;
}
ret = eglInitialize(drm->disp, &major, &minor);
if (!ret) {
log_err("cannot init egl display for %s", node);
ret = -EFAULT;
goto err_gbm;
}
ext = eglQueryString(drm->disp, EGL_EXTENSIONS);
if (!ext || !strstr(ext, "EGL_KHR_surfaceless_opengl")) {
log_err("surfaceless opengl not supported");
ret = -EFAULT;
goto err_disp;
}
api = EGL_OPENGL_ES_API;
/* TODO: allow api = EGL_OPENGL_API */
if (!eglBindAPI(api)) {
log_err("cannot bind opengl-es api");
ret = -EFAULT;
goto err_disp;
}
drm->ctx = eglCreateContext(drm->disp, NULL, EGL_NO_CONTEXT, ctx_att);
if (!drm->ctx) {
log_err("cannot create egl context");
ret = -EFAULT;
goto err_disp;
}
/*
* We allow only one global video object. See uterm_video_new for the
* reasons. If we every change this we need proper context-management
* and should remove this call here.
*/
if (!eglMakeCurrent(drm->disp, EGL_NO_SURFACE, EGL_NO_SURFACE,
drm->ctx)) {
log_err("cannot activate egl context");
ret = -EFAULT;
goto err_ctx;
}
ret = ev_eloop_new_fd(video->eloop, &drm->efd, drm->fd,
EV_READABLE, event, video);
if (ret)
goto err_ctx;
video->flags |= VIDEO_HOTPLUG;
log_info("new drm device via %s", node);
return 0;
err_ctx:
eglDestroyContext(drm->disp, drm->ctx);
err_disp:
eglTerminate(drm->disp);
err_gbm:
gbm_device_destroy(drm->gbm);
err_close:
close(drm->fd);
return ret;
}
static int video_init(struct uterm_video *video)
{
struct udev_enumerate *e;
struct udev_list_entry *name;
const char *path;
struct udev_device *dev;
int ret;
ret = udev_monitor_filter_add_match_subsystem_devtype(video->umon,
"drm", "drm_minor");
if (ret) {
log_err("cannot add udev filter (%d): %m", ret);
return -EFAULT;
}
ret = udev_monitor_enable_receiving(video->umon);
if (ret) {
log_err("cannot start udev_monitor (%d): %m", ret);
return -EFAULT;
}
e = udev_enumerate_new(video->udev);
if (!e) {
log_err("cannot create udev_enumerate object");
return -EFAULT;
}
ret = udev_enumerate_add_match_subsystem(e, "drm");
if (ret) {
log_err("cannot add udev match (%d): %m", ret);
ret = -EFAULT;
goto err_enum;
}
ret = udev_enumerate_add_match_sysname(e, "card[0-9]*");
if (ret) {
log_err("cannot add udev match (%d): %m", ret);
ret = -EFAULT;
goto err_enum;
}
ret = udev_enumerate_scan_devices(e);
if (ret) {
log_err("cannot scan udev devices (%d): %m", ret);
ret = -EFAULT;
goto err_enum;
}
udev_list_entry_foreach(name, udev_enumerate_get_list_entry(e)) {
path = udev_list_entry_get_name(name);
if (!path || !*path)
continue;
dev = udev_device_new_from_syspath(video->udev, path);
if (!dev)
continue;
/* TODO: more sophisticated device selection */
ret = init_device(video, dev);
udev_device_unref(dev);
if (!ret)
goto done;
}
ret = -ENODEV;
log_err("no drm device found");
goto err_enum;
done:
ret = 0;
err_enum:
udev_enumerate_unref(e);
return ret;
}
static void video_destroy(struct uterm_video *video)
{
struct drm_video *drm = &video->drm;
struct uterm_display *disp;
while ((disp = video->displays)) {
video->displays = disp->next;
disp->next = NULL;
unbind_display(disp);
}
log_info("free drm device");
ev_eloop_rm_fd(drm->efd);
eglDestroyContext(drm->disp, drm->ctx);
eglTerminate(drm->disp);
gbm_device_destroy(drm->gbm);
drmDropMaster(drm->fd);
close(drm->fd);
}
static int hotplug(struct uterm_video *video)
{
drmModeRes *res;
drmModeConnector *conn;
struct uterm_display *disp, *tmp;
unsigned int i;
if (!video_is_awake(video) || !video_need_hotplug(video))
return 0;
res = drmModeGetResources(video->drm.fd);
if (!res) {
log_err("cannot retrieve drm resources");
return -EACCES;
}
for (disp = video->displays; disp; disp = disp->next)
disp->flags &= ~DISPLAY_AVAILABLE;
for (i = 0; i < res->count_connectors; ++i) {
conn = drmModeGetConnector(video->drm.fd, res->connectors[i]);
if (!conn)
continue;
if (conn->connection == DRM_MODE_CONNECTED) {
for (disp = video->displays; disp; disp = disp->next) {
if (disp->drm.conn_id == res->connectors[i]) {
disp->flags |= DISPLAY_AVAILABLE;
break;
}
}
if (!disp)
bind_display(video, res, conn);
}
drmModeFreeConnector(conn);
}
drmModeFreeResources(res);
while (video->displays) {
tmp = video->displays;
if (tmp->flags & DISPLAY_AVAILABLE)
break;
video->displays = tmp->next;
tmp->next = NULL;
unbind_display(tmp);
}
for (disp = video->displays; disp && disp->next; ) {
tmp = disp->next;
if (tmp->flags & DISPLAY_AVAILABLE) {
disp = tmp;
} else {
disp->next = tmp->next;
tmp->next = NULL;
unbind_display(tmp);
}
}
video->flags &= ~VIDEO_HOTPLUG;
return 0;
}
static int monitor(struct uterm_video *video)
{
struct udev_device *dev;
const char *action, *val;
unsigned int id;
dev = udev_monitor_receive_device(video->umon);
if (!dev) {
log_warn("cannot receive device from udev_monitor");
return 0;
}
action = udev_device_get_action(dev);
if (!action)
goto ignore;
if (strcmp(action, "change"))
goto ignore;
val = udev_device_get_property_value(dev, "HOTPLUG");
if (strcmp(val, "1"))
goto ignore;
id = get_id(dev);
if (id != video->drm.id)
goto ignore;
video->flags |= VIDEO_HOTPLUG;
ignore:
udev_device_unref(dev);
return 0;
}
static int video_poll(struct uterm_video *video, int mask)
{
int ret;
if (mask & (EV_HUP | EV_ERR)) {
log_err("udev_monitor closed unexpectedly");
return -EFAULT;
}
if (mask & EV_READABLE) {
ret = monitor(video);
if (ret)
return ret;
}
return hotplug(video);
}
static void video_sleep(struct uterm_video *video)
{
if (!video_is_awake(video))
return;
drmDropMaster(video->drm.fd);
video->flags &= ~VIDEO_AWAKE;
}
static int video_wake_up(struct uterm_video *video)
{
int ret;
if (video_is_awake(video))
return 0;
ret = drmSetMaster(video->drm.fd);
if (ret) {
log_err("cannot set DRM-master");
return -EACCES;
}
video->flags |= VIDEO_AWAKE;
ret = hotplug(video);
if (ret) {
video->flags &= ~VIDEO_AWAKE;
drmDropMaster(video->drm.fd);
return ret;
}
return 0;
}
const struct mode_ops drm_mode_ops = {
.init = NULL,
.destroy = NULL,
.get_name = mode_get_name,
.get_width = mode_get_width,
.get_height = mode_get_height,
};
const struct display_ops drm_display_ops = {
.init = NULL,
.destroy = NULL,
.activate = display_activate,
.deactivate = display_deactivate,
.set_dpms = display_set_dpms,
.use = display_use,
.swap = display_swap,
};
const struct video_ops drm_video_ops = {
.init = video_init,
.destroy = video_destroy,
.segfault = NULL, /* TODO: reset all saved CRTCs on segfault */
.poll = video_poll,
.sleep = video_sleep,
.wake_up = video_wake_up,
};