| /* |
| * 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" |
| |
| #if defined(ENABLE_IMXGPU) |
| #if defined(ENABLE_OPENGL) |
| #include "gl-renderer.h" |
| #endif |
| #if defined(ENABLE_IMXG2D) |
| #include "g2d-renderer.h" |
| #endif |
| #endif |
| |
| #include "linux-dmabuf.h" |
| #include "linux-dmabuf-unstable-v1-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; |
| int use_pixman; |
| #if defined(ENABLE_IMXGPU) |
| NativeDisplayType display; |
| #if defined(ENABLE_IMXG2D) |
| int use_g2d; |
| int clone_mode; |
| char clone_device[50]; |
| #endif |
| #endif |
| }; |
| |
| 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; |
| #if defined(ENABLE_IMXGPU) |
| NativeDisplayType display; |
| NativeWindowType window; |
| #endif |
| }; |
| |
| #if defined(ENABLE_IMXGPU) |
| #if defined(ENABLE_OPENGL) |
| struct gl_renderer_interface *gl_renderer; |
| #endif |
| #if defined(ENABLE_IMXG2D) |
| struct g2d_renderer_interface *g2d_renderer; |
| #endif |
| #endif |
| |
| 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_pixman(struct weston_output *base, pixman_region32_t *damage) |
| { |
| 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 |
| fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage, |
| void *repaint_data) |
| { |
| struct fbdev_output *output = to_fbdev_output(base); |
| struct fbdev_backend *fbb = output->backend; |
| struct weston_compositor *ec = fbb->compositor; |
| if (fbb->use_pixman) { |
| fbdev_output_repaint_pixman(base,damage); |
| } else { |
| ec->renderer->repaint_output(base, damage); |
| /* Update the damage region. */ |
| pixman_region32_subtract(&ec->primary_plane.damage, |
| &ec->primary_plane.damage, damage); |
| 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: |
| /* vinfo->grayscale != 0 in 8mqevk board |
| 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_disable(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); |
| |
| output->base.start_repaint_loop = fbdev_output_start_repaint_loop; |
| output->base.repaint = fbdev_output_repaint; |
| |
| if (backend->use_pixman) { |
| /* 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; |
| } |
| if (pixman_renderer_output_create(&output->base, |
| PIXMAN_RENDERER_OUTPUT_USE_SHADOW) < 0) |
| goto out_hw_surface; |
| #if defined(ENABLE_IMXGPU) |
| #if defined(ENABLE_IMXG2D) |
| } else if (backend->use_g2d) { |
| const char *g2d_device = head->device; |
| if (backend->clone_mode) |
| g2d_device = &(backend->clone_device[0]); |
| |
| if (g2d_renderer->fbdev_output_create(&output->base, |
| backend->compositor->wl_display, |
| g2d_device) < 0) { |
| weston_log("g2d_renderer_output_create failed.\n"); |
| goto out; |
| } |
| #endif |
| #if defined(ENABLE_OPENGL) |
| } else { |
| output->window = fbCreateWindow(backend->display, -1, -1, 0, 0); |
| if (output->window == NULL) { |
| fprintf(stderr, "failed to create window\n"); |
| return 0; |
| } |
| if (gl_renderer->output_window_create(&output->base, |
| (EGLNativeWindowType)output->window, (void *)output->window, |
| gl_renderer->opaque_attribs, |
| NULL, 0) < 0) { |
| weston_log("gl_renderer_output_create failed.\n"); |
| goto out; |
| } |
| #endif |
| #endif |
| } |
| |
| 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); |
| out: |
| return -1; |
| } |
| |
| static int |
| fbdev_output_disable(struct weston_output *base) |
| { |
| struct fbdev_output *output = to_fbdev_output(base); |
| struct fbdev_backend *backend = to_fbdev_backend(base->compositor); |
| |
| if (!base->enabled) |
| return 0; |
| |
| wl_event_source_remove(output->finish_frame_timer); |
| output->finish_frame_timer = NULL; |
| |
| if (backend->use_pixman) { |
| pixman_renderer_output_destroy(&output->base); |
| fbdev_frame_buffer_unmap(output); |
| } |
| #if defined(ENABLE_IMXGPU) |
| if(output->window) { |
| fbDestroyWindow(output->window); |
| output->window = NULL; |
| } |
| if(output->display) { |
| fbDestroyDisplay(output->display); |
| output->display = NULL; |
| } |
| #endif |
| 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, device); |
| 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); |
| struct fbdev_backend *backend = output->backend; |
| |
| weston_log("Destroying fbdev output.\n"); |
| |
| fbdev_output_disable(base); |
| |
| if (backend->use_pixman) { |
| if (base->renderer_state != NULL) |
| pixman_renderer_output_destroy(base); |
| #if defined(ENABLE_IMXGPU) |
| #if defined(ENABLE_IMXG2D) |
| } else if (backend->use_g2d) { |
| g2d_renderer->output_destroy(base); |
| #endif |
| #if defined(ENABLE_OPENGL) |
| } else { |
| gl_renderer->output_destroy(base); |
| #endif |
| #endif |
| } |
| |
| /* 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 (backend->use_pixman) { |
| 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 char * |
| find_framebuffer_device(struct fbdev_backend *b, const char *seat) |
| { |
| struct udev_enumerate *e; |
| struct udev_list_entry *entry; |
| const char *path, *device_seat, *id; |
| char *fb_device_path = NULL; |
| struct udev_device *device, *fb_device, *pci; |
| |
| e = udev_enumerate_new(b->udev); |
| udev_enumerate_add_match_subsystem(e, "graphics"); |
| udev_enumerate_add_match_sysname(e, "fb[0-9]*"); |
| |
| udev_enumerate_scan_devices(e); |
| fb_device = NULL; |
| udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { |
| bool is_boot_vga = false; |
| |
| path = udev_list_entry_get_name(entry); |
| device = udev_device_new_from_syspath(b->udev, path); |
| if (!device) |
| continue; |
| device_seat = udev_device_get_property_value(device, "ID_SEAT"); |
| if (!device_seat) |
| device_seat = default_seat; |
| if (strcmp(device_seat, seat)) { |
| udev_device_unref(device); |
| continue; |
| } |
| |
| pci = udev_device_get_parent_with_subsystem_devtype(device, |
| "pci", NULL); |
| if (pci) { |
| id = udev_device_get_sysattr_value(pci, "boot_vga"); |
| if (id && !strcmp(id, "1")) |
| is_boot_vga = true; |
| } |
| |
| /* If a framebuffer device was found, and this device isn't |
| * the boot-VGA device, don't use it. */ |
| if (!is_boot_vga && fb_device) { |
| udev_device_unref(device); |
| continue; |
| } |
| |
| /* There can only be one boot_vga device. Try to use it |
| * at all costs. */ |
| if (is_boot_vga) { |
| if (fb_device) |
| udev_device_unref(fb_device); |
| fb_device = device; |
| break; |
| } |
| |
| /* Per the (!is_boot_vga && fb_device) test above, only |
| * trump existing saved devices with boot-VGA devices, so if |
| * the test ends up here, this must be the first device seen. */ |
| assert(!fb_device); |
| fb_device = device; |
| } |
| |
| udev_enumerate_unref(e); |
| |
| if (fb_device) { |
| fb_device_path = strdup(udev_device_get_devnode(fb_device)); |
| udev_device_unref(fb_device); |
| } |
| |
| return fb_device_path; |
| } |
| |
| 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; |
| } |
| |
| if (!param->device) |
| param->device = find_framebuffer_device(backend, seat_id); |
| if (!param->device) { |
| weston_log("fatal: no framebuffer devices detected.\n"); |
| goto out_udev; |
| } |
| |
| /* 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; |
| backend->use_pixman = param->use_pixman; |
| #if defined(ENABLE_IMXGPU) && defined(ENABLE_IMXG2D) |
| backend->use_g2d = param->use_g2d; |
| backend->clone_mode = param->clone_mode; |
| memcpy(&backend->clone_device[0], param->device, strlen(param->device)); |
| #endif |
| backend->output_transform = param->output_transform; |
| |
| weston_setup_vt_switch_bindings(compositor); |
| |
| if (backend->use_pixman) { |
| if (pixman_renderer_init(compositor) < 0) |
| goto out_launcher; |
| if (!fbdev_head_create(backend, param->device)) |
| goto out_launcher; |
| #if defined(ENABLE_IMXGPU) |
| #if defined(ENABLE_IMXG2D) |
| } else if (backend->use_g2d) { |
| int i = 0, k = 0, count = 0; |
| int disp_count = 0; |
| char displays[5][32]; |
| g2d_renderer = weston_load_module("g2d-renderer.so", |
| "g2d_renderer_interface"); |
| if (!g2d_renderer) { |
| weston_log("could not load g2d renderer\n"); |
| goto out_launcher; |
| } |
| |
| if (g2d_renderer->create(backend->compositor) < 0) { |
| weston_log("g2d_renderer_create failed.\n"); |
| goto out_launcher; |
| } |
| |
| count = strlen(param->device); |
| for (i= 0; i < count; i++) { |
| if (param->device[i] == ',') { |
| displays[disp_count][k] = '\0'; |
| disp_count++; |
| k = 0; |
| continue; |
| } |
| displays[disp_count][k++] = param->device[i]; |
| } |
| displays[disp_count][k] = '\0'; |
| disp_count++; |
| if(backend->clone_mode) { |
| if (!fbdev_head_create(backend, displays[0])) |
| goto out_launcher; |
| } else { |
| for(i = 0; i < disp_count; i++) { |
| if (!fbdev_head_create(backend, displays[i])) |
| goto out_launcher; |
| } |
| } |
| weston_log("param->device = %s, disp_count = %d, backend->clone_mode = %d\n", |
| param->device, disp_count, backend->clone_mode); |
| #endif |
| #if defined(ENABLE_OPENGL) |
| } else { |
| gl_renderer = weston_load_module("gl-renderer.so", |
| "gl_renderer_interface"); |
| if (!gl_renderer) { |
| weston_log("could not load gl renderer\n"); |
| goto out_launcher; |
| } |
| |
| /* determine which framebuffer we got so we don't use |
| * FB_FRAMBUFFER_%d to override default displayId |
| */ |
| int index = -1; |
| sscanf(param->device, "/dev/fb%d", &index); |
| |
| if (index < 0) { |
| weston_log("Failed to get proper frambuffer\n"); |
| goto out_launcher; |
| } |
| |
| backend->display = fbGetDisplayByIndex(index); |
| if (backend->display == NULL) { |
| weston_log("fbGetDisplay failed.\n"); |
| goto out_launcher; |
| } |
| if (gl_renderer->display_create(compositor, NO_EGL_PLATFORM, |
| backend->display, |
| NULL, |
| gl_renderer->opaque_attribs, |
| NULL, 0) < 0) { |
| weston_log("gl_renderer_create failed.\n"); |
| goto out_launcher; |
| } |
| if (!fbdev_head_create(backend, param->device)) |
| goto out_launcher; |
| #endif |
| #endif |
| } |
| |
| if (compositor->renderer->import_dmabuf) { |
| if (linux_dmabuf_setup(compositor) < 0) |
| weston_log("Error: initializing dmabuf " |
| "support failed.\n"); |
| } |
| |
| free(param->device); |
| |
| udev_input_init(&backend->input, compositor, backend->udev, |
| seat_id, param->configure_device); |
| |
| return backend; |
| |
| out_launcher: |
| free(param->device); |
| 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) |
| { |
| config->tty = 0; /* default to current tty */ |
| config->device = NULL; |
| config->output_transform = WL_OUTPUT_TRANSFORM_NORMAL; |
| #if !defined(ENABLE_IMXGPU) || !defined(ENABLE_OPENGL) && !defined(ENABLE_IMXG2D) |
| config->use_pixman = 1; |
| #else |
| config->use_pixman = 0; |
| #endif |
| #if defined(ENABLE_IMXGPU) && defined(ENABLE_IMXG2D) |
| #if !defined(ENABLE_OPENGL) |
| config->use_g2d = 1; |
| #else |
| config->use_g2d = 0; |
| #endif |
| config->clone_mode = 0; |
| #endif |
| 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; |
| } |