2 * uterm - Linux User-Space Terminal fbdev module
4 * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@googlemail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files
8 * (the "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
14 * The above copyright notice and this permission notice shall be included
15 * in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 #include <sys/ioctl.h>
40 #include "uterm_fbdev_internal.h"
41 #include "uterm_video.h"
42 #include "uterm_video_internal.h"
44 #define LOG_SUBSYSTEM "video_fbdev"
46 static int mode_init(struct uterm_mode *mode)
48 struct fbdev_mode *fbdev;
50 fbdev = malloc(sizeof(*fbdev));
53 memset(fbdev, 0, sizeof(*fbdev));
59 static void mode_destroy(struct uterm_mode *mode)
64 static const char *mode_get_name(const struct uterm_mode *mode)
69 static unsigned int mode_get_width(const struct uterm_mode *mode)
71 struct fbdev_mode *fbdev = mode->data;
76 static unsigned int mode_get_height(const struct uterm_mode *mode)
78 struct fbdev_mode *fbdev = mode->data;
83 static const struct mode_ops fbdev_mode_ops = {
85 .destroy = mode_destroy,
86 .get_name = mode_get_name,
87 .get_width = mode_get_width,
88 .get_height = mode_get_height,
91 static int display_init(struct uterm_display *disp)
93 struct fbdev_display *fbdev;
95 fbdev = malloc(sizeof(*fbdev));
98 memset(fbdev, 0, sizeof(*fbdev));
100 disp->dpms = UTERM_DPMS_UNKNOWN;
105 static void display_destroy(struct uterm_display *disp)
110 static int refresh_info(struct uterm_display *disp)
113 struct fbdev_display *dfb = disp->data;
115 ret = ioctl(dfb->fd, FBIOGET_FSCREENINFO, &dfb->finfo);
117 log_err("cannot get finfo (%d): %m", errno);
121 ret = ioctl(dfb->fd, FBIOGET_VSCREENINFO, &dfb->vinfo);
123 log_err("cannot get vinfo (%d): %m", errno);
130 static int display_activate_force(struct uterm_display *disp,
131 struct uterm_mode *mode,
134 /* TODO: Add support for 24-bpp. However, we need to check how 3-bytes
135 * integers are assembled in big/little/mixed endian systems. */
136 static const char depths[] = { 32, 16, 0 };
137 struct fbdev_display *dfb = disp->data;
138 struct uterm_mode *m;
139 struct fbdev_mode *mfb;
140 struct fb_var_screeninfo *vinfo;
141 struct fb_fix_screeninfo *finfo;
147 if (!force && (disp->flags & DISPLAY_ONLINE))
150 /* TODO: We do not support explicit modesetting in fbdev, so we require
151 * @mode to be NULL. You can still switch modes via "fbset" on the
152 * console and then restart the app. It will automatically adapt to the
153 * new mode. The only values changed here are bpp and color mode. */
157 dfb->fd = open(dfb->node, O_RDWR | O_CLOEXEC | O_NONBLOCK);
159 log_err("cannot open %s (%d): %m", dfb->node, errno);
163 ret = refresh_info(disp);
172 vinfo->activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
173 vinfo->xres_virtual = vinfo->xres;
174 vinfo->yres_virtual = vinfo->yres * 2;
175 disp->flags |= DISPLAY_DBUF;
177 /* udlfb is broken as it reports the sizes of the virtual framebuffer
178 * (even mmap() accepts it) but the actual size that we can access
179 * without segfaults is the _real_ framebuffer. Therefore, disable
180 * double-buffering for it.
181 * TODO: fix this kernel-side!
182 * TODO: There are so many broken fbdev drivers that just accept any
183 * virtual FB sizes and then break mmap that we now disable
184 * double-buffering entirely. We might instead add a white-list or
185 * optional command-line argument to re-enable it. */
186 if (true || !strcmp(finfo->id, "udlfb")) {
187 disp->flags &= ~DISPLAY_DBUF;
188 vinfo->yres_virtual = vinfo->yres;
191 ret = ioctl(dfb->fd, FBIOPUT_VSCREENINFO, vinfo);
193 disp->flags &= ~DISPLAY_DBUF;
194 vinfo->yres_virtual = vinfo->yres;
195 ret = ioctl(dfb->fd, FBIOPUT_VSCREENINFO, vinfo);
197 log_debug("cannot reset fb offsets (%d): %m", errno);
202 if (disp->flags & DISPLAY_DBUF)
203 log_debug("enable double buffering");
205 log_debug("disable double buffering");
207 ret = refresh_info(disp);
211 /* We require TRUECOLOR mode here. That is, each pixel has a color value
212 * that is split into rgba values that we can set directly. Other visual
213 * modes like pseudocolor or direct-color do not provide this. As I have
214 * never seen a device that does not support TRUECOLOR, I think we can
215 * ignore them here. */
216 if (finfo->visual != FB_VISUAL_TRUECOLOR ||
217 vinfo->bits_per_pixel != 32) {
218 for (i = 0; depths[i]; ++i) {
219 vinfo->bits_per_pixel = depths[i];
220 vinfo->activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
222 ret = ioctl(dfb->fd, FBIOPUT_VSCREENINFO,
227 ret = refresh_info(disp);
231 if (finfo->visual == FB_VISUAL_TRUECOLOR)
236 if (vinfo->bits_per_pixel != 32 &&
237 vinfo->bits_per_pixel != 16) {
238 log_error("device %s does not support 16/32 bpp but: %u",
239 dfb->node, vinfo->bits_per_pixel);
244 if (vinfo->xres_virtual < vinfo->xres ||
245 (disp->flags & DISPLAY_DBUF &&
246 vinfo->yres_virtual < vinfo->yres * 2) ||
247 vinfo->yres_virtual < vinfo->yres) {
248 log_warning("device %s has weird virtual buffer sizes (%d %d %d %d)",
249 dfb->node, vinfo->xres, vinfo->xres_virtual,
250 vinfo->yres, vinfo->yres_virtual);
253 if (finfo->visual != FB_VISUAL_TRUECOLOR) {
254 log_error("device %s does not support true-color",
260 if (vinfo->red.length > 8 ||
261 vinfo->green.length > 8 ||
262 vinfo->blue.length > 8) {
263 log_error("device %s uses unusual color-ranges",
269 log_info("activating display %s to %ux%u %u bpp", dfb->node,
270 vinfo->xres, vinfo->yres, vinfo->bits_per_pixel);
272 /* calculate monitor rate, default is 60 Hz */
273 quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres);
274 quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres);
275 quot *= vinfo->pixclock;
277 dfb->rate = 1000000000000000LLU / quot;
279 dfb->rate = 60 * 1000;
280 log_warning("cannot read monitor refresh rate, forcing 60 Hz");
283 if (dfb->rate == 0) {
284 log_warning("monitor refresh rate is 0 Hz, forcing it to 1 Hz");
286 } else if (dfb->rate > 200000) {
287 log_warning("monitor refresh rate is >200 Hz (%u Hz), forcing it to 200 Hz",
292 val = 1000000 / dfb->rate;
293 display_set_vblank_timer(disp, val);
294 log_debug("vblank timer: %u ms, monitor refresh rate: %u Hz", val,
297 len = finfo->line_length * vinfo->yres;
298 if (disp->flags & DISPLAY_DBUF)
301 dfb->map = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, dfb->fd, 0);
302 if (dfb->map == MAP_FAILED) {
303 log_error("cannot mmap device %s (%d): %m", dfb->node,
309 memset(dfb->map, 0, len);
310 dfb->xres = vinfo->xres;
311 dfb->yres = vinfo->yres;
313 dfb->stride = finfo->line_length;
315 dfb->Bpp = vinfo->bits_per_pixel / 8;
316 dfb->off_r = vinfo->red.offset;
317 dfb->len_r = vinfo->red.length;
318 dfb->off_g = vinfo->green.offset;
319 dfb->len_g = vinfo->green.length;
320 dfb->off_b = vinfo->blue.offset;
321 dfb->len_b = vinfo->blue.length;
327 if (dfb->len_r == 8 && dfb->len_g == 8 && dfb->len_b == 8 &&
328 dfb->off_r == 16 && dfb->off_g == 8 && dfb->off_b == 0 &&
331 else if (dfb->len_r == 5 && dfb->len_g == 6 && dfb->len_b == 5 &&
332 dfb->off_r == 11 && dfb->off_g == 5 && dfb->off_b == 0 &&
336 /* TODO: make dithering configurable */
337 disp->flags |= DISPLAY_DITHERING;
339 if (disp->current_mode) {
340 m = disp->current_mode;
342 ret = mode_new(&m, &fbdev_mode_ops);
345 ret = uterm_mode_bind(m, disp);
350 disp->current_mode = m;
355 mfb->width = dfb->xres;
356 mfb->height = dfb->yres;
358 disp->flags |= DISPLAY_ONLINE;
362 munmap(dfb->map, dfb->len);
368 static int display_activate(struct uterm_display *disp, struct uterm_mode *mode)
370 return display_activate_force(disp, mode, false);
373 static void display_deactivate_force(struct uterm_display *disp, bool force)
375 struct fbdev_display *dfb = disp->data;
377 log_info("deactivating device %s", dfb->node);
380 memset(dfb->map, 0, dfb->len);
381 munmap(dfb->map, dfb->len);
386 uterm_mode_unbind(disp->current_mode);
387 disp->current_mode = NULL;
388 disp->flags &= ~DISPLAY_ONLINE;
392 static void display_deactivate(struct uterm_display *disp)
394 return display_deactivate_force(disp, false);
397 static int display_set_dpms(struct uterm_display *disp, int state)
400 struct fbdev_display *dfb = disp->data;
404 set = FB_BLANK_UNBLANK;
406 case UTERM_DPMS_STANDBY:
407 set = FB_BLANK_NORMAL;
409 case UTERM_DPMS_SUSPEND:
410 set = FB_BLANK_NORMAL;
413 set = FB_BLANK_POWERDOWN;
419 log_info("setting DPMS of device %p to %s", dfb->node,
420 uterm_dpms_to_name(state));
422 ret = ioctl(dfb->fd, FBIOBLANK, set);
424 log_error("cannot set DPMS on %s (%d): %m", dfb->node,
433 static int display_use(struct uterm_display *disp, bool *opengl)
435 struct fbdev_display *dfb = disp->data;
440 if (!(disp->flags & DISPLAY_DBUF))
443 return dfb->bufid ^ 1;
446 static int display_get_buffers(struct uterm_display *disp,
447 struct uterm_video_buffer *buffer,
448 unsigned int formats)
450 struct fbdev_display *dfb = disp->data;
451 unsigned int f = 0, i;
454 f = UTERM_FORMAT_XRGB32;
456 f = UTERM_FORMAT_RGB16;
461 for (i = 0; i < 2; ++i) {
462 buffer[i].width = dfb->xres;
463 buffer[i].height = dfb->yres;
464 buffer[i].stride = dfb->stride;
465 buffer[i].format = f;
466 if (!(disp->flags & DISPLAY_DBUF) || !i)
467 buffer[i].data = dfb->map;
469 buffer[i].data = &dfb->map[dfb->yres * dfb->stride];
475 static int display_swap(struct uterm_display *disp, bool immediate)
477 struct fbdev_display *dfb = disp->data;
478 struct fb_var_screeninfo *vinfo;
481 if (!(disp->flags & DISPLAY_DBUF)) {
484 return display_schedule_vblank_timer(disp);
489 vinfo->activate = FB_ACTIVATE_NOW;
491 vinfo->activate = FB_ACTIVATE_VBL;
494 vinfo->yoffset = dfb->yres;
498 ret = ioctl(dfb->fd, FBIOPUT_VSCREENINFO, vinfo);
500 log_warning("cannot swap buffers on %s (%d): %m",
506 return display_schedule_vblank_timer(disp);
509 static const struct display_ops fbdev_display_ops = {
510 .init = display_init,
511 .destroy = display_destroy,
512 .activate = display_activate,
513 .deactivate = display_deactivate,
514 .set_dpms = display_set_dpms,
516 .get_buffers = display_get_buffers,
517 .swap = display_swap,
518 .blit = uterm_fbdev_display_blit,
519 .fake_blendv = uterm_fbdev_display_fake_blendv,
520 .fill = uterm_fbdev_display_fill,
523 static void intro_idle_event(struct ev_eloop *eloop, void *unused, void *data)
525 struct uterm_video *video = data;
526 struct fbdev_video *vfb = video->data;
527 struct uterm_display *disp;
528 struct fbdev_display *dfb;
531 vfb->pending_intro = false;
532 ev_eloop_unregister_idle_cb(eloop, intro_idle_event, data, EV_NORMAL);
534 ret = display_new(&disp, &fbdev_display_ops);
536 log_error("cannot create fbdev display: %d", ret);
541 dfb->node = vfb->node;
542 ret = uterm_display_bind(disp, video);
544 log_error("cannot bind fbdev display: %d", ret);
545 uterm_display_unref(disp);
549 uterm_display_unref(disp);
552 static int video_init(struct uterm_video *video, const char *node)
555 struct fbdev_video *vfb;
557 log_info("new device on %s", node);
559 vfb = malloc(sizeof(*vfb));
562 memset(vfb, 0, sizeof(*vfb));
565 vfb->node = strdup(node);
571 ret = ev_eloop_register_idle_cb(video->eloop, intro_idle_event, video,
574 log_error("cannot register idle event: %d", ret);
577 vfb->pending_intro = true;
588 static void video_destroy(struct uterm_video *video)
590 struct fbdev_video *vfb = video->data;
592 log_info("free device on %s", vfb->node);
594 if (vfb->pending_intro)
595 ev_eloop_unregister_idle_cb(video->eloop, intro_idle_event,
602 static void video_sleep(struct uterm_video *video)
604 struct uterm_display *iter;
607 shl_dlist_for_each(i, &video->displays) {
608 iter = shl_dlist_entry(i, struct uterm_display, list);
610 if (!display_is_online(iter))
613 display_deactivate_force(iter, true);
617 static int video_wake_up(struct uterm_video *video)
619 struct uterm_display *iter;
623 video->flags |= VIDEO_AWAKE;
624 shl_dlist_for_each(i, &video->displays) {
625 iter = shl_dlist_entry(i, struct uterm_display, list);
627 if (!display_is_online(iter))
630 ret = display_activate_force(iter, NULL, true);
634 if (iter->dpms != UTERM_DPMS_UNKNOWN)
635 display_set_dpms(iter, iter->dpms);
641 static const struct video_ops fbdev_video_ops = {
643 .destroy = video_destroy,
644 .segfault = NULL, /* TODO */
646 .sleep = video_sleep,
647 .wake_up = video_wake_up,
650 static const struct uterm_video_module fbdev_module = {
651 .ops = &fbdev_video_ops,
654 const struct uterm_video_module *UTERM_VIDEO_FBDEV = &fbdev_module;