uterm_video: add dummy fbdev backend

The fbdev backend is non-functional, but we add it for documentational
purposes. It is not built as part of kmscon.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
This commit is contained in:
David Herrmann 2012-03-23 11:46:39 +01:00
parent 95e434eddb
commit 62a3db3e9c

589
src/uterm_video_fbdev.c Normal file
View File

@ -0,0 +1,589 @@
/*
* 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.
*/
/*
* FBDEV Video backend
* This is an example how to write a backend for uterm_video that is based on
* classic linux-fbdev. It is unfinished and should not be used, yet. However,
* if we ever need this it should be easy to finish it.
*/
#warning "uterm_video fbdev backend is unfinished"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libudev.h>
#include <linux/fb.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include "log.h"
#include "uterm.h"
#include "uterm_internal.h"
static const char *mode_get_name(const struct uterm_mode *mode)
{
return NULL;
}
static unsigned int mode_get_width(const struct uterm_mode *mode)
{
return mode->fbdev.unused;
}
static unsigned int mode_get_height(const struct uterm_mode *mode)
{
return mode->fbdev.unused;
}
static int display_activate(struct uterm_display *disp, struct uterm_mode *mode)
{
struct fb_var_screeninfo *info;
int ret;
uint64_t quot;
size_t len;
if (!disp->video || !(disp->flags & DISPLAY_OPEN))
return -EINVAL;
/* TODO: we currently use the current mode of the framebuffer and do not
* allow changing this mode. However, we should rather list valid modes
* when opening the device and set the framebuffer to the requested mode
* here first.
* For now you can use the fbset(1) program to modify
* frambuffer-resolutions and timers.
*
* info->bits_per_pixel = 32 is almost a prerequisite here! We also need
* to check for TRUECOLOR. Everything else is just old-school.
*/
info = &disp->fbdev.vinfo;
info->xoffset = 0;
info->yoffset = 0;
info->activate = FB_ACTIVATE_NOW;
info->xres_virtual = info->xres;
info->yres_virtual = info->yres * 2;
log_info("video_fbdev: activating display %p to %ux%u", disp,
info->xres, info->yres);
ret = ioctl(disp->fbdev.fd, FBIOPUT_VSCREENINFO, info);
if (ret) {
log_err("video_fbdev: cannot set vinfo (%d): %m", errno);
return -EFAULT;
}
/* vinfo/finfo may have changed so refetch them */
ret = ioctl(disp->fbdev.fd, FBIOGET_VSCREENINFO, &info);
if (ret) {
log_err("video_fbdev: cannot get vinfo (%d): %m", errno);
return -EFAULT;
}
ret = ioctl(disp->fbdev.fd, FBIOGET_FSCREENINFO, &disp->fbdev.finfo);
if (ret) {
log_err("video_fbdev: cannot get finfo (%d): %m", errno);
return -EFAULT;
}
quot = (info->upper_margin + info->lower_margin + info->yres);
quot *= (info->left_margin + info->right_margin + info->xres);
quot *= info->pixclock;
disp->fbdev.rate = quot ? (1000000000000000LLU / quot) : 0;
if (!disp->fbdev.rate)
disp->fbdev.rate = 60 * 1000; /* 60 Hz by default */
len = disp->fbdev.finfo.line_length * disp->fbdev.vinfo.yres_virtual;
disp->fbdev.map = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED,
disp->fbdev.fd, 0);
if (disp->fbdev.map == MAP_FAILED) {
log_err("video_fbdev: cannot mmap framebuffer (%d): %m", errno);
return -EFAULT;
}
memset(disp->fbdev.map, 0, len);
disp->fbdev.len = len;
disp->current_mode = mode;
disp->flags |= DISPLAY_ONLINE;
return 0;
}
static void display_deactivate(struct uterm_display *disp)
{
if (!disp->video || !(disp->flags & DISPLAY_ONLINE))
return;
log_info("video_fbdev: deactivating display %p", disp);
disp->current_mode = NULL;
disp->flags &= ~DISPLAY_ONLINE;
munmap(disp->fbdev.map, disp->fbdev.len);
}
static int display_set_dpms(struct uterm_display *disp, int state)
{
int set, ret;
if (!disp->video || !(disp->flags & DISPLAY_OPEN))
return -EINVAL;
switch (state) {
case UTERM_DPMS_ON:
set = FB_BLANK_UNBLANK;
break;
case UTERM_DPMS_STANDBY:
set = FB_BLANK_NORMAL;
break;
case UTERM_DPMS_SUSPEND:
set = FB_BLANK_NORMAL;
break;
case UTERM_DPMS_OFF:
set = FB_BLANK_POWERDOWN;
break;
default:
return -EINVAL;
}
log_info("video_fbdev: setting DPMS of display %p to %s", disp,
uterm_dpms_to_name(state));
ret = ioctl(disp->fbdev.fd, FBIOBLANK, set);
if (ret) {
log_err("video_fbdev: cannot set DPMS on %p (%d): %m", disp,
errno);
return -EFAULT;
}
disp->dpms = state;
return 0;
}
void *fbdev_display_map(struct uterm_display *disp)
{
if (!disp->video || !(disp->flags & DISPLAY_OPEN))
return NULL;
if (!(disp->flags & DISPLAY_ONLINE))
return NULL;
/* TODO: temporary function to obtain a pointer to the frambuffer from
* the calling application. Stuff like bpp, size, etc. must be
* published, too, otherwise, no-one will be able to use it.
*/
return disp->fbdev.map;
}
static int display_use(struct uterm_display *disp)
{
if (!disp->video || !(disp->flags & DISPLAY_ONLINE))
return -EINVAL;
if (!(disp->flags & DISPLAY_OPEN))
return -EINVAL;
return 0;
}
static int display_swap(struct uterm_display *disp)
{
if (!disp->video || !(disp->flags & DISPLAY_ONLINE))
return -EINVAL;
if (!(disp->flags & DISPLAY_OPEN) || (disp->dpms != UTERM_DPMS_ON))
return -EINVAL;
/* TODO: swap */
/*
vinfo.activate = FB_ACTIVATE_VBL;
vinfo.yoffset = 0 or vinfo.vres;
ioctl(fd, FBIOPUT_VSCREENINFO, &info)
*/
return 0;
}
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, "fb", 2) || !name[2])
return -ENODEV;
devnum = strtol(&name[2], &end, 10);
if (devnum < 0 || *end)
return -ENODEV;
return devnum;
}
static int open_display(struct uterm_display *disp)
{
int ret;
if (!(disp->video->flags & VIDEO_AWAKE))
return -EINVAL;
if (disp->flags & DISPLAY_OPEN)
return 0;
disp->fbdev.fd = open(disp->fbdev.node, O_RDWR | O_CLOEXEC);
if (disp->fbdev.fd < 0) {
log_err("video_fbdev: cannot open %s (%d): %m",
disp->fbdev.node, errno);
return -EFAULT;
}
ret = ioctl(disp->fbdev.fd, FBIOGET_FSCREENINFO, &disp->fbdev.finfo);
if (ret) {
log_err("video_fbdev: cannot get finfo (%d): %m", errno);
ret = -EFAULT;
goto err_fd;
}
ret = ioctl(disp->fbdev.fd, FBIOGET_VSCREENINFO, &disp->fbdev.vinfo);
if (ret) {
log_err("video_fbdev: cannot get vinfo (%d): %m", errno);
ret = -EFAULT;
goto err_fd;
}
disp->flags |= DISPLAY_OPEN;
if (disp->flags & DISPLAY_ONLINE) {
ret = display_activate(disp, NULL);
if (ret)
goto err_open;
}
return 0;
err_open:
disp->flags &= ~DISPLAY_OPEN;
err_fd:
close(disp->fbdev.fd);
return ret;
}
static void close_display(struct uterm_display *disp)
{
if (!(disp->flags & DISPLAY_OPEN))
return;
close(disp->fbdev.fd);
disp->flags &= ~DISPLAY_OPEN;
if (disp->fbdev.map)
munmap(disp->fbdev.map, disp->fbdev.len);
}
static int init_display(struct uterm_video *video, struct udev_device *dev)
{
struct uterm_display *disp;
int ret, id;
const char *node;
id = get_id(dev);
if (id < 0)
return id;
ret = display_new(&disp, &fbdev_display_ops);
if (ret)
return ret;
log_info("video_fbdev: probing %s", udev_device_get_sysname(dev));
node = udev_device_get_devnode(dev);
if (!node) {
log_err("video_fbdev: cannot get device node");
return -ENODEV;
}
disp->fbdev.node = mem_strdup(node);
if (!disp->fbdev.node) {
ret = -ENOMEM;
goto err_free;
}
disp->video = video;
disp->fbdev.id = id;
disp->dpms = UTERM_DPMS_UNKNOWN;
if (video->flags & VIDEO_AWAKE) {
ret = open_display(disp);
if (ret)
goto err_str;
}
disp->next = video->displays;
video->displays = disp;
return 0;
err_str:
disp->video = NULL;
mem_free(disp->fbdev.node);
err_free:
uterm_display_unref(disp);
return ret;
}
static void destroy_display(struct uterm_display *disp)
{
display_deactivate(disp);
close_display(disp);
mem_free(disp->fbdev.node);
disp->video = NULL;
uterm_display_unref(disp);
}
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,
"graphics", NULL);
if (ret) {
log_err("video_fbdev: cannot add udev filter (%d): %m", ret);
return -EFAULT;
}
ret = udev_monitor_enable_receiving(video->umon);
if (ret) {
log_err("video_fbdev: cannot start udev_monitor (%d): %m", ret);
return -EFAULT;
}
e = udev_enumerate_new(video->udev);
if (!e) {
log_err("video_fbdev: cannot create udev_enumerate object");
return -EFAULT;
}
ret = udev_enumerate_add_match_subsystem(e, "graphics");
if (ret) {
log_err("video_fbdev: cannot add udev match (%d): %m", ret);
ret = -EFAULT;
goto err_enum;
}
ret = udev_enumerate_add_match_sysname(e, "fb[0-9]*");
if (ret) {
log_err("video_fbdev: cannot add udev match (%d): %m", ret);
ret = -EFAULT;
goto err_enum;
}
ret = udev_enumerate_scan_devices(e);
if (ret) {
log_err("video_fbdev: 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;
init_display(video, dev);
udev_device_unref(dev);
}
log_info("video_fbdev: new fbdev device");
return 0;
err_enum:
udev_enumerate_unref(e);
return ret;
}
static void video_destroy(struct uterm_video *video)
{
struct uterm_display *disp;
log_info("video_fbdev: free fbdev device");
while ((disp = video->displays)) {
video->displays = disp->next;
disp->next = NULL;
destroy_display(disp);
}
}
static int hotplug(struct uterm_video *video)
{
struct udev_device *dev;
const char *action;
unsigned int id;
struct uterm_display *disp, *tmp;
dev = udev_monitor_receive_device(video->umon);
if (!dev) {
log_warn("video_fbdev: cannot receive device from udev_monitor");
return 0;
}
action = udev_device_get_action(dev);
if (!action)
goto ignore;
if (!strcmp(action, "add")) {
init_display(video, dev);
} else if (!strcmp(action, "remove")) {
if (!video->displays)
goto ignore;
id = get_id(dev);
if (id < 0)
goto ignore;
disp = NULL;
if (video->displays->fbdev.id == id) {
disp = video->displays;
video->displays = disp->next;
} else for (tmp = video->displays; tmp->next; tmp = tmp->next) {
if (tmp->next->fbdev.id == id) {
disp = tmp->next;
tmp->next = tmp->next->next;
break;
}
}
if (disp) {
disp->next = NULL;
destroy_display(disp);
}
}
ignore:
udev_device_unref(dev);
return 0;
}
static int video_poll(struct uterm_video *video, unsigned int num)
{
unsigned int i;
struct epoll_event *ev;
int ret;
for (i = 0; i < num; ++i) {
ev = &video->efd_evs[i];
if (ev->data.fd == video->umon_fd) {
if (ev->events & (EPOLLERR | EPOLLHUP)) {
log_err("video_fbdev: udev_monitor closed unexpectedly");
return -EFAULT;
}
if (ev->events & (EPOLLIN)) {
ret = hotplug(video);
if (ret)
return ret;
}
}
}
return 0;
}
static void video_sleep(struct uterm_video *video)
{
struct uterm_display *disp;
if (!(video->flags & VIDEO_AWAKE))
return;
for (disp = video->displays; disp; disp = disp->next)
close_display(disp);
video->flags &= ~VIDEO_AWAKE;
}
static int video_wake_up(struct uterm_video *video)
{
struct uterm_display *disp, *tmp;
int ret;
if (video->flags & VIDEO_AWAKE)
return 0;
video->flags |= VIDEO_AWAKE;
while (video->displays) {
tmp = video->displays;
ret = open_display(tmp);
if (!ret)
break;
video->displays = tmp->next;
tmp->next = NULL;
destroy_display(tmp);
}
for (disp = video->displays; disp && disp->next; ) {
tmp = disp->next;
ret = open_display(tmp);
if (!ret) {
disp = tmp;
} else {
disp->next = tmp->next;
tmp->next = NULL;
destroy_display(tmp);
}
}
return 0;
}
const struct mode_ops fbdev_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 fbdev_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 fbdev_video_ops = {
.init = video_init,
.destroy = video_destroy,
.segfault = NULL, /* TODO */
.poll = video_poll,
.sleep = video_sleep,
.wake_up = video_wake_up,
};