/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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_head { struct weston_head base; /* Frame buffer details. */ char *device; struct fbdev_screeninfo fb_info; }; struct fbdev_output { struct fbdev_backend *backend; struct weston_output base; struct weston_mode mode; struct wl_event_source *finish_frame_timer; /* framebuffer mmap details */ size_t buffer_length; void *fb; /* pixman details. */ pixman_image_t *hw_surface; }; static const char default_seat[] = "seat0"; static inline struct fbdev_head * to_fbdev_head(struct weston_head *base) { return container_of(base, struct fbdev_head, base); } 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 struct fbdev_head * fbdev_output_get_head(struct fbdev_output *output) { if (wl_list_length(&output->base.head_list) != 1) return NULL; return container_of(output->base.head_list.next, struct fbdev_head, base.output_link); } 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) { struct fbdev_head *head; int retval = -1; head = fbdev_output_get_head(output); 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->buffer_length = head->fb_info.buffer_length; output->fb = mmap(NULL, output->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(head->fb_info.pixel_format, head->fb_info.x_resolution, head->fb_info.y_resolution, output->fb, head->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->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->buffer_length) < 0) weston_log("Failed to munmap frame buffer: %s\n", strerror(errno)); output->fb = NULL; } static int fbdev_output_attach_head(struct weston_output *output_base, struct weston_head *head_base) { struct fbdev_output *output = to_fbdev_output(output_base); struct fbdev_head *head = to_fbdev_head(head_base); /* Clones not supported. */ if (!wl_list_empty(&output->base.head_list)) return -1; /* only one static mode in list */ output->mode.flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; output->mode.width = head->fb_info.x_resolution; output->mode.height = head->fb_info.y_resolution; output->mode.refresh = head->fb_info.refresh_rate; wl_list_init(&output->base.mode_list); wl_list_insert(&output->base.mode_list, &output->mode.link); output->base.current_mode = &output->mode; return 0; } 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); struct fbdev_head *head; int fb_fd; struct wl_event_loop *loop; head = fbdev_output_get_head(output); /* Create the frame buffer. */ fb_fd = fbdev_frame_buffer_open(head->device, &head->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, PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 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 struct fbdev_head * fbdev_head_create(struct fbdev_backend *backend, const char *device) { struct fbdev_head *head; int fb_fd; head = zalloc(sizeof *head); if (!head) return NULL; head->device = strdup(device); /* Create the frame buffer. */ fb_fd = fbdev_frame_buffer_open(head->device, &head->fb_info); if (fb_fd < 0) { weston_log("Creating frame buffer head failed.\n"); goto out_free; } close(fb_fd); weston_head_init(&head->base, "fbdev"); weston_head_set_connection_status(&head->base, true); weston_head_set_monitor_strings(&head->base, "unknown", head->fb_info.id, NULL); weston_head_set_subpixel(&head->base, WL_OUTPUT_SUBPIXEL_UNKNOWN); weston_head_set_physical_size(&head->base, head->fb_info.width_mm, head->fb_info.height_mm); weston_compositor_add_head(backend->compositor, &head->base); weston_log("Created head '%s' for device %s (%s)\n", head->base.name, head->device, head->base.model); return head; out_free: free(head->device); free(head); return NULL; } static void fbdev_head_destroy(struct fbdev_head *head) { weston_head_release(&head->base); free(head->device); free(head); } static struct weston_output * fbdev_output_create(struct weston_compositor *compositor, const char *name) { struct fbdev_output *output; weston_log("Creating fbdev output.\n"); output = zalloc(sizeof *output); if (output == NULL) return NULL; output->backend = to_fbdev_backend(compositor); weston_output_init(&output->base, compositor, name); output->base.destroy = fbdev_output_destroy; output->base.disable = fbdev_output_disable; output->base.enable = fbdev_output_enable; output->base.attach_head = fbdev_output_attach_head; weston_compositor_add_pending_output(&output->base, compositor); return &output->base; } 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); } /* 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_head *head; struct fbdev_screeninfo new_screen_info; int fb_fd; head = fbdev_output_get_head(output); weston_log("Re-enabling fbdev output.\n"); assert(output->base.enabled); /* Create the frame buffer. */ fb_fd = fbdev_frame_buffer_open(head->device, &new_screen_info); if (fb_fd < 0) { weston_log("Creating frame buffer failed.\n"); return -1; } /* Check whether the frame buffer details have changed since we were * disabled. */ if (compare_screen_info(&head->fb_info, &new_screen_info) != 0) { /* Perform a mode-set to restore the old mode. */ if (fbdev_set_screen_info(fb_fd, &head->fb_info) < 0) { weston_log("Failed to restore mode settings. " "Attempting to re-open output anyway.\n"); } close(fb_fd); /* Disable and enable the output so that resources depending on * the frame buffer X/Y resolution (such as the shadow buffer) * are re-initialised. */ fbdev_output_disable(&output->base); return fbdev_output_enable(&output->base); } /* 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"); return -1; } return 0; } static void fbdev_backend_destroy(struct weston_compositor *base) { struct fbdev_backend *backend = to_fbdev_backend(base); struct weston_head *head, *next; udev_input_destroy(&backend->input); /* Destroy the output. */ weston_compositor_shutdown(base); wl_list_for_each_safe(head, next, &base->head_list, compositor_link) fbdev_head_destroy(to_fbdev_head(head)); /* 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; const char *session_seat; session_seat = getenv("XDG_SEAT"); if (session_seat) seat_id = session_seat; if (param->seat_id) seat_id = param->seat_id; 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, seat_id, 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->base.create_output = fbdev_output_create; backend->prev_state = WESTON_COMPOSITOR_ACTIVE; weston_setup_vt_switch_bindings(compositor); if (pixman_renderer_init(compositor) < 0) goto out_launcher; if (!fbdev_head_create(backend, param->device)) 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 */ config->seat_id = NULL; } 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; }