weston/libweston/compositor-fbdev.c
Pekka Paalanen 01f60211b2 libweston: introduce weston_head
In order to support clone modes, libweston needs the concept of a head
that is separate from weston_output. While weston_output manages buffers
and the repaint state machine, weston_head will represent a single
monitor. In the future it will be possible to have a single
weston_output drive one or more weston_heads for a clone mode that
shares the framebuffers between all cloned heads.

All the fields that are obviously properties of the monitor are moved
from weston_output into weston_head.

As moving the fields requires one to touch all the backends for all the
assingments, introduce setter functions for them while we are here. The
setters are identical to the old assignments, for now.

As a temporary measure, weston_output embeds a single head. Also the
ugly casts in weston_head_set_monitor_strings() will be removed by a
follow-up patch.

Libweston major version is bumped, because weston_output struct layout
is changed.

v7:
- Bump libweston major version.

v6:
- adapt to upstream changes in weston_output_set_transform()

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
v5 Reviewed-by: Derek Foreman <derekf@osg.samsung.com>
v6 Reviewed-by: Ian Ray <ian.ray@ge.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
Acked-by: Derek Foreman <derekf@osg.samsung.com>
2018-04-10 14:31:05 +03:00

802 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright © 2008-2011 Kristian Høgsberg
* Copyright © 2011 Intel Corporation
* Copyright © 2012 Raspberry Pi Foundation
* Copyright © 2013 Philip Withnall
*
* 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 (including the
* next paragraph) 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.
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fb.h>
#include <linux/input.h>
#include <assert.h>
#include <libudev.h>
#include "shared/helpers.h"
#include "compositor.h"
#include "compositor-fbdev.h"
#include "launcher-util.h"
#include "pixman-renderer.h"
#include "libinput-seat.h"
#include "presentation-time-server-protocol.h"
struct fbdev_backend {
struct weston_backend base;
struct weston_compositor *compositor;
uint32_t prev_state;
struct udev *udev;
struct udev_input input;
uint32_t output_transform;
struct wl_listener session_listener;
};
struct fbdev_screeninfo {
unsigned int x_resolution; /* pixels, visible area */
unsigned int y_resolution; /* pixels, visible area */
unsigned int width_mm; /* visible screen width in mm */
unsigned int height_mm; /* visible screen height in mm */
unsigned int bits_per_pixel;
size_t buffer_length; /* length of frame buffer memory in bytes */
size_t line_length; /* length of a line in bytes */
char id[16]; /* screen identifier */
pixman_format_code_t pixel_format; /* frame buffer pixel format */
unsigned int refresh_rate; /* Hertz */
};
struct fbdev_output {
struct fbdev_backend *backend;
struct weston_output base;
struct weston_mode mode;
struct wl_event_source *finish_frame_timer;
/* Frame buffer details. */
char *device;
struct fbdev_screeninfo fb_info;
void *fb; /* length is fb_info.buffer_length */
/* pixman details. */
pixman_image_t *hw_surface;
};
static const char default_seat[] = "seat0";
static inline struct fbdev_output *
to_fbdev_output(struct weston_output *base)
{
return container_of(base, struct fbdev_output, base);
}
static inline struct fbdev_backend *
to_fbdev_backend(struct weston_compositor *base)
{
return container_of(base->backend, struct fbdev_backend, base);
}
static void
fbdev_output_start_repaint_loop(struct weston_output *output)
{
struct timespec ts;
weston_compositor_read_presentation_clock(output->compositor, &ts);
weston_output_finish_frame(output, &ts, WP_PRESENTATION_FEEDBACK_INVALID);
}
static int
fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage,
void *repaint_data)
{
struct fbdev_output *output = to_fbdev_output(base);
struct weston_compositor *ec = output->base.compositor;
/* Repaint the damaged region onto the back buffer. */
pixman_renderer_output_set_buffer(base, output->hw_surface);
ec->renderer->repaint_output(base, damage);
/* Update the damage region. */
pixman_region32_subtract(&ec->primary_plane.damage,
&ec->primary_plane.damage, damage);
/* Schedule the end of the frame. We do not sync this to the frame
* buffer clock because users who want that should be using the DRM
* compositor. FBIO_WAITFORVSYNC blocks and FB_ACTIVATE_VBL requires
* panning, which is broken in most kernel drivers.
*
* Finish the frame synchronised to the specified refresh rate. The
* refresh rate is given in mHz and the interval in ms. */
wl_event_source_timer_update(output->finish_frame_timer,
1000000 / output->mode.refresh);
return 0;
}
static int
finish_frame_handler(void *data)
{
struct fbdev_output *output = data;
struct timespec ts;
weston_compositor_read_presentation_clock(output->base.compositor, &ts);
weston_output_finish_frame(&output->base, &ts, 0);
return 1;
}
static pixman_format_code_t
calculate_pixman_format(struct fb_var_screeninfo *vinfo,
struct fb_fix_screeninfo *finfo)
{
/* Calculate the pixman format supported by the frame buffer from the
* buffer's metadata. Return 0 if no known pixman format is supported
* (since this has depth 0 it's guaranteed to not conflict with any
* actual pixman format).
*
* Documentation on the vinfo and finfo structures:
* http://www.mjmwired.net/kernel/Documentation/fb/api.txt
*
* TODO: Try a bit harder to support other formats, including setting
* the preferred format in the hardware. */
int type;
weston_log("Calculating pixman format from:\n"
STAMP_SPACE " - type: %i (aux: %i)\n"
STAMP_SPACE " - visual: %i\n"
STAMP_SPACE " - bpp: %i (grayscale: %i)\n"
STAMP_SPACE " - red: offset: %i, length: %i, MSB: %i\n"
STAMP_SPACE " - green: offset: %i, length: %i, MSB: %i\n"
STAMP_SPACE " - blue: offset: %i, length: %i, MSB: %i\n"
STAMP_SPACE " - transp: offset: %i, length: %i, MSB: %i\n",
finfo->type, finfo->type_aux, finfo->visual,
vinfo->bits_per_pixel, vinfo->grayscale,
vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right,
vinfo->green.offset, vinfo->green.length,
vinfo->green.msb_right,
vinfo->blue.offset, vinfo->blue.length,
vinfo->blue.msb_right,
vinfo->transp.offset, vinfo->transp.length,
vinfo->transp.msb_right);
/* We only handle packed formats at the moment. */
if (finfo->type != FB_TYPE_PACKED_PIXELS)
return 0;
/* We only handle true-colour frame buffers at the moment. */
switch(finfo->visual) {
case FB_VISUAL_TRUECOLOR:
case FB_VISUAL_DIRECTCOLOR:
if (vinfo->grayscale != 0)
return 0;
break;
default:
return 0;
}
/* We only support formats with MSBs on the left. */
if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 ||
vinfo->blue.msb_right != 0)
return 0;
/* Work out the format type from the offsets. We only support RGBA and
* ARGB at the moment. */
type = PIXMAN_TYPE_OTHER;
if ((vinfo->transp.offset >= vinfo->red.offset ||
vinfo->transp.length == 0) &&
vinfo->red.offset >= vinfo->green.offset &&
vinfo->green.offset >= vinfo->blue.offset)
type = PIXMAN_TYPE_ARGB;
else if (vinfo->red.offset >= vinfo->green.offset &&
vinfo->green.offset >= vinfo->blue.offset &&
vinfo->blue.offset >= vinfo->transp.offset)
type = PIXMAN_TYPE_RGBA;
if (type == PIXMAN_TYPE_OTHER)
return 0;
/* Build the format. */
return PIXMAN_FORMAT(vinfo->bits_per_pixel, type,
vinfo->transp.length,
vinfo->red.length,
vinfo->green.length,
vinfo->blue.length);
}
static int
calculate_refresh_rate(struct fb_var_screeninfo *vinfo)
{
uint64_t quot;
/* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz. */
quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres);
quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres);
quot *= vinfo->pixclock;
if (quot > 0) {
uint64_t refresh_rate;
refresh_rate = 1000000000000000LLU / quot;
if (refresh_rate > 200000)
refresh_rate = 200000; /* cap at 200 Hz */
if (refresh_rate >= 1000) /* at least 1 Hz */
return refresh_rate;
}
return 60 * 1000; /* default to 60 Hz */
}
static int
fbdev_query_screen_info(int fd, struct fbdev_screeninfo *info)
{
struct fb_var_screeninfo varinfo;
struct fb_fix_screeninfo fixinfo;
/* Probe the device for screen information. */
if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 ||
ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) {
return -1;
}
/* Store the pertinent data. */
info->x_resolution = varinfo.xres;
info->y_resolution = varinfo.yres;
info->width_mm = varinfo.width;
info->height_mm = varinfo.height;
info->bits_per_pixel = varinfo.bits_per_pixel;
info->buffer_length = fixinfo.smem_len;
info->line_length = fixinfo.line_length;
strncpy(info->id, fixinfo.id, sizeof(info->id));
info->id[sizeof(info->id)-1] = '\0';
info->pixel_format = calculate_pixman_format(&varinfo, &fixinfo);
info->refresh_rate = calculate_refresh_rate(&varinfo);
if (info->pixel_format == 0) {
weston_log("Frame buffer uses an unsupported format.\n");
return -1;
}
return 1;
}
static int
fbdev_set_screen_info(int fd, struct fbdev_screeninfo *info)
{
struct fb_var_screeninfo varinfo;
/* Grab the current screen information. */
if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) {
return -1;
}
/* Update the information. */
varinfo.xres = info->x_resolution;
varinfo.yres = info->y_resolution;
varinfo.width = info->width_mm;
varinfo.height = info->height_mm;
varinfo.bits_per_pixel = info->bits_per_pixel;
/* Try to set up an ARGB (x8r8g8b8) pixel format. */
varinfo.grayscale = 0;
varinfo.transp.offset = 24;
varinfo.transp.length = 0;
varinfo.transp.msb_right = 0;
varinfo.red.offset = 16;
varinfo.red.length = 8;
varinfo.red.msb_right = 0;
varinfo.green.offset = 8;
varinfo.green.length = 8;
varinfo.green.msb_right = 0;
varinfo.blue.offset = 0;
varinfo.blue.length = 8;
varinfo.blue.msb_right = 0;
/* Set the device's screen information. */
if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) {
return -1;
}
return 1;
}
/* Returns an FD for the frame buffer device. */
static int
fbdev_frame_buffer_open(const char *fb_dev,
struct fbdev_screeninfo *screen_info)
{
int fd = -1;
weston_log("Opening fbdev frame buffer.\n");
/* Open the frame buffer device. */
fd = open(fb_dev, O_RDWR | O_CLOEXEC);
if (fd < 0) {
weston_log("Failed to open frame buffer device %s: %s\n",
fb_dev, strerror(errno));
return -1;
}
/* Grab the screen info. */
if (fbdev_query_screen_info(fd, screen_info) < 0) {
weston_log("Failed to get frame buffer info: %s\n",
strerror(errno));
close(fd);
return -1;
}
return fd;
}
/* Closes the FD on success or failure. */
static int
fbdev_frame_buffer_map(struct fbdev_output *output, int fd)
{
int retval = -1;
weston_log("Mapping fbdev frame buffer.\n");
/* Map the frame buffer. Write-only mode, since we don't want to read
* anything back (because it's slow). */
output->fb = mmap(NULL, output->fb_info.buffer_length,
PROT_WRITE, MAP_SHARED, fd, 0);
if (output->fb == MAP_FAILED) {
weston_log("Failed to mmap frame buffer: %s\n",
strerror(errno));
output->fb = NULL;
goto out_close;
}
/* Create a pixman image to wrap the memory mapped frame buffer. */
output->hw_surface =
pixman_image_create_bits(output->fb_info.pixel_format,
output->fb_info.x_resolution,
output->fb_info.y_resolution,
output->fb,
output->fb_info.line_length);
if (output->hw_surface == NULL) {
weston_log("Failed to create surface for frame buffer.\n");
goto out_unmap;
}
/* Success! */
retval = 0;
out_unmap:
if (retval != 0 && output->fb != NULL) {
munmap(output->fb, output->fb_info.buffer_length);
output->fb = NULL;
}
out_close:
if (fd >= 0)
close(fd);
return retval;
}
static void
fbdev_frame_buffer_unmap(struct fbdev_output *output)
{
if (!output->fb) {
assert(!output->hw_surface);
return;
}
weston_log("Unmapping fbdev frame buffer.\n");
if (output->hw_surface)
pixman_image_unref(output->hw_surface);
output->hw_surface = NULL;
if (munmap(output->fb, output->fb_info.buffer_length) < 0)
weston_log("Failed to munmap frame buffer: %s\n",
strerror(errno));
output->fb = NULL;
}
static void fbdev_output_destroy(struct weston_output *base);
static int
fbdev_output_enable(struct weston_output *base)
{
struct fbdev_output *output = to_fbdev_output(base);
struct fbdev_backend *backend = to_fbdev_backend(base->compositor);
int fb_fd;
struct wl_event_loop *loop;
/* Create the frame buffer. */
fb_fd = fbdev_frame_buffer_open(output->device, &output->fb_info);
if (fb_fd < 0) {
weston_log("Creating frame buffer failed.\n");
return -1;
}
if (fbdev_frame_buffer_map(output, fb_fd) < 0) {
weston_log("Mapping frame buffer failed.\n");
return -1;
}
output->base.start_repaint_loop = fbdev_output_start_repaint_loop;
output->base.repaint = fbdev_output_repaint;
if (pixman_renderer_output_create(&output->base) < 0)
goto out_hw_surface;
loop = wl_display_get_event_loop(backend->compositor->wl_display);
output->finish_frame_timer =
wl_event_loop_add_timer(loop, finish_frame_handler, output);
weston_log("fbdev output %d×%d px\n",
output->mode.width, output->mode.height);
weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n",
output->mode.refresh / 1000);
return 0;
out_hw_surface:
fbdev_frame_buffer_unmap(output);
return -1;
}
static int
fbdev_output_disable(struct weston_output *base)
{
struct fbdev_output *output = to_fbdev_output(base);
if (!base->enabled)
return 0;
wl_event_source_remove(output->finish_frame_timer);
output->finish_frame_timer = NULL;
pixman_renderer_output_destroy(&output->base);
fbdev_frame_buffer_unmap(output);
return 0;
}
static int
fbdev_output_create(struct fbdev_backend *backend,
const char *device)
{
struct fbdev_output *output;
struct weston_head *head;
int fb_fd;
weston_log("Creating fbdev output.\n");
output = zalloc(sizeof *output);
if (output == NULL)
return -1;
output->backend = backend;
output->device = strdup(device);
/* Create the frame buffer. */
fb_fd = fbdev_frame_buffer_open(device, &output->fb_info);
if (fb_fd < 0) {
weston_log("Creating frame buffer failed.\n");
goto out_free;
}
weston_output_init(&output->base, backend->compositor, "fbdev");
output->base.destroy = fbdev_output_destroy;
output->base.disable = fbdev_output_disable;
output->base.enable = fbdev_output_enable;
/* only one static mode in list */
output->mode.flags =
WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED;
output->mode.width = output->fb_info.x_resolution;
output->mode.height = output->fb_info.y_resolution;
output->mode.refresh = output->fb_info.refresh_rate;
wl_list_insert(&output->base.mode_list, &output->mode.link);
output->base.current_mode = &output->mode;
head = &output->base.head;
weston_head_set_monitor_strings(head, "unknown", output->fb_info.id,
NULL);
weston_head_set_subpixel(head, WL_OUTPUT_SUBPIXEL_UNKNOWN);
weston_head_set_physical_size(head, output->fb_info.width_mm,
output->fb_info.height_mm);
close(fb_fd);
weston_compositor_add_pending_output(&output->base, backend->compositor);
return 0;
out_free:
free(output->device);
free(output);
return -1;
}
static void
fbdev_output_destroy(struct weston_output *base)
{
struct fbdev_output *output = to_fbdev_output(base);
weston_log("Destroying fbdev output.\n");
fbdev_output_disable(base);
/* Remove the output. */
weston_output_release(&output->base);
free(output->device);
free(output);
}
/* strcmp()-style return values. */
static int
compare_screen_info (const struct fbdev_screeninfo *a,
const struct fbdev_screeninfo *b)
{
if (a->x_resolution == b->x_resolution &&
a->y_resolution == b->y_resolution &&
a->width_mm == b->width_mm &&
a->height_mm == b->height_mm &&
a->bits_per_pixel == b->bits_per_pixel &&
a->pixel_format == b->pixel_format &&
a->refresh_rate == b->refresh_rate)
return 0;
return 1;
}
static int
fbdev_output_reenable(struct fbdev_backend *backend,
struct weston_output *base)
{
struct fbdev_output *output = to_fbdev_output(base);
struct fbdev_screeninfo new_screen_info;
int fb_fd;
char *device;
weston_log("Re-enabling fbdev output.\n");
/* Create the frame buffer. */
fb_fd = fbdev_frame_buffer_open(output->device, &new_screen_info);
if (fb_fd < 0) {
weston_log("Creating frame buffer failed.\n");
goto err;
}
/* Check whether the frame buffer details have changed since we were
* disabled. */
if (compare_screen_info (&output->fb_info, &new_screen_info) != 0) {
/* Perform a mode-set to restore the old mode. */
if (fbdev_set_screen_info(fb_fd, &output->fb_info) < 0) {
weston_log("Failed to restore mode settings. "
"Attempting to re-open output anyway.\n");
}
close(fb_fd);
/* Remove and re-add the output so that resources depending on
* the frame buffer X/Y resolution (such as the shadow buffer)
* are re-initialised. */
device = strdup(output->device);
fbdev_output_destroy(&output->base);
fbdev_output_create(backend, device);
free(device);
return 0;
}
/* Map the device if it has the same details as before. */
if (fbdev_frame_buffer_map(output, fb_fd) < 0) {
weston_log("Mapping frame buffer failed.\n");
goto err;
}
return 0;
err:
return -1;
}
static void
fbdev_backend_destroy(struct weston_compositor *base)
{
struct fbdev_backend *backend = to_fbdev_backend(base);
udev_input_destroy(&backend->input);
/* Destroy the output. */
weston_compositor_shutdown(base);
/* Chain up. */
weston_launcher_destroy(base->launcher);
udev_unref(backend->udev);
free(backend);
}
static void
session_notify(struct wl_listener *listener, void *data)
{
struct weston_compositor *compositor = data;
struct fbdev_backend *backend = to_fbdev_backend(compositor);
struct weston_output *output;
if (compositor->session_active) {
weston_log("entering VT\n");
compositor->state = backend->prev_state;
wl_list_for_each(output, &compositor->output_list, link) {
fbdev_output_reenable(backend, output);
}
weston_compositor_damage_all(compositor);
udev_input_enable(&backend->input);
} else {
weston_log("leaving VT\n");
udev_input_disable(&backend->input);
wl_list_for_each(output, &compositor->output_list, link) {
fbdev_frame_buffer_unmap(to_fbdev_output(output));
}
backend->prev_state = compositor->state;
weston_compositor_offscreen(compositor);
/* If we have a repaint scheduled (from the idle handler), make
* sure we cancel that so we don't try to pageflip when we're
* vt switched away. The OFFSCREEN state will prevent
* further attempts at repainting. When we switch
* back, we schedule a repaint, which will process
* pending frame callbacks. */
wl_list_for_each(output,
&compositor->output_list, link) {
output->repaint_needed = false;
}
}
}
static struct fbdev_backend *
fbdev_backend_create(struct weston_compositor *compositor,
struct weston_fbdev_backend_config *param)
{
struct fbdev_backend *backend;
const char *seat_id = default_seat;
weston_log("initializing fbdev backend\n");
backend = zalloc(sizeof *backend);
if (backend == NULL)
return NULL;
backend->compositor = compositor;
compositor->backend = &backend->base;
if (weston_compositor_set_presentation_clock_software(
compositor) < 0)
goto out_compositor;
backend->udev = udev_new();
if (backend->udev == NULL) {
weston_log("Failed to initialize udev context.\n");
goto out_compositor;
}
/* Set up the TTY. */
backend->session_listener.notify = session_notify;
wl_signal_add(&compositor->session_signal,
&backend->session_listener);
compositor->launcher =
weston_launcher_connect(compositor, param->tty, "seat0", false);
if (!compositor->launcher) {
weston_log("fatal: fbdev backend should be run using "
"weston-launch binary, or your system should "
"provide the logind D-Bus API.\n");
goto out_udev;
}
backend->base.destroy = fbdev_backend_destroy;
backend->prev_state = WESTON_COMPOSITOR_ACTIVE;
weston_setup_vt_switch_bindings(compositor);
if (pixman_renderer_init(compositor) < 0)
goto out_launcher;
if (fbdev_output_create(backend, param->device) < 0)
goto out_launcher;
udev_input_init(&backend->input, compositor, backend->udev,
seat_id, param->configure_device);
return backend;
out_launcher:
weston_launcher_destroy(compositor->launcher);
out_udev:
udev_unref(backend->udev);
out_compositor:
weston_compositor_shutdown(compositor);
free(backend);
return NULL;
}
static void
config_init_to_defaults(struct weston_fbdev_backend_config *config)
{
/* TODO: Ideally, available frame buffers should be enumerated using
* udev, rather than passing a device node in as a parameter. */
config->tty = 0; /* default to current tty */
config->device = "/dev/fb0"; /* default frame buffer */
}
WL_EXPORT int
weston_backend_init(struct weston_compositor *compositor,
struct weston_backend_config *config_base)
{
struct fbdev_backend *b;
struct weston_fbdev_backend_config config = {{ 0, }};
if (config_base == NULL ||
config_base->struct_version != WESTON_FBDEV_BACKEND_CONFIG_VERSION ||
config_base->struct_size > sizeof(struct weston_fbdev_backend_config)) {
weston_log("fbdev backend config structure is invalid\n");
return -1;
}
config_init_to_defaults(&config);
memcpy(&config, config_base, config_base->struct_size);
b = fbdev_backend_create(compositor, &config);
if (b == NULL)
return -1;
return 0;
}