summaryrefslogtreecommitdiff
path: root/twin_fbdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'twin_fbdev.c')
-rw-r--r--twin_fbdev.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/twin_fbdev.c b/twin_fbdev.c
new file mode 100644
index 0000000..a1d9472
--- /dev/null
+++ b/twin_fbdev.c
@@ -0,0 +1,502 @@
+/*
+ * Linux fbdev driver for Twin
+ *
+ * Copyright 2006 Benjamin Herrenschmidt <benh@kernel.crashing.org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Twin Library; see the file COPYING. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <stdarg.h>
+#include <byteswap.h>
+
+#include <linux/vt.h>
+#include <linux/kd.h>
+#include <linux/input.h>
+
+#include "twin_fbdev.h"
+
+/* We might want to have more error logging options */
+#define SERROR(fmt...) do { fprintf(stderr, fmt); \
+ fprintf(stderr, " : %s\n", strerror(errno)); \
+ } while(0)
+#define IERROR(fmt...) fprintf(stderr, fmt)
+
+#define DEBUG(fmt...) printf(fmt)
+//#define DEBUG(fmt...)
+
+/* Only one instance can exist */
+static twin_fbdev_t *twin_fb;
+static int vt_switch_pending;
+
+static void _twin_fbdev_put_span (twin_coord_t left,
+ twin_coord_t top,
+ twin_coord_t right,
+ twin_argb32_t *pixels,
+ void *closure)
+{
+ twin_fbdev_t *tf = closure;
+ twin_coord_t width = right - left;
+ unsigned int *dest;
+
+ if (!tf->active || tf->fb_base == MAP_FAILED)
+ return;
+
+ dest = (unsigned int *)(tf->fb_ptr + top * tf->fb_fix.line_length);
+ dest += left;
+ while(width--)
+ *(dest++) = *(pixels++);
+}
+
+static twin_bool_t twin_fbdev_apply_config(twin_fbdev_t *tf)
+{
+ off_t off, pgsize = getpagesize();
+ struct fb_cmap cmap;
+ size_t len;
+
+ /* Tweak fields to default to 32 bpp argb and virtual == phys */
+ tf->fb_var.xres_virtual = tf->fb_var.xres;
+ tf->fb_var.yres_virtual = tf->fb_var.yres;
+ tf->fb_var.bits_per_pixel = 32;
+ tf->fb_var.red.length = 8;
+ tf->fb_var.green.length = 8;
+ tf->fb_var.blue.length = 8;
+ tf->fb_var.transp.length = 8;
+ tf->fb_var.red.offset = 0;
+ tf->fb_var.green.offset = 0;
+ tf->fb_var.blue.offset = 0;
+ tf->fb_var.transp.offset = 0;
+
+ /* Apply fbdev settings */
+ if (ioctl(tf->fb_fd, FBIOPUT_VSCREENINFO, &tf->fb_var) < 0) {
+ SERROR("can't set fb mode");
+ return 0;
+ }
+
+ /* Get new fbdev configuration */
+ if (ioctl(tf->fb_fd, FBIOGET_VSCREENINFO, tf->fb_var) < 0) {
+ SERROR("can't get framebuffer config");
+ return 0;
+ }
+
+ DEBUG("fbdev set config set to:\n");
+ DEBUG(" xres = %d\n", tf->fb_var.xres);
+ DEBUG(" yres = %d\n", tf->fb_var.yres);
+ DEBUG(" xres_virtual = %d\n", tf->fb_var.xres_virtual);
+ DEBUG(" yres_virtual = %d\n", tf->fb_var.yres_virtual);
+ DEBUG(" bits_per_pix = %d\n", tf->fb_var.bits_per_pixel);
+ DEBUG(" red.len/off = %d/%d\n",
+ tf->fb_var.red.length, tf->fb_var.red.offset);
+ DEBUG(" green.len/off = %d/%d\n",
+ tf->fb_var.green.length, tf->fb_var.green.offset);
+ DEBUG(" blue.len/off = %d/%d\n",
+ tf->fb_var.blue.length, tf->fb_var.blue.offset);
+ DEBUG(" trans.len/off = %d/%d\n",
+ tf->fb_var.transp.length, tf->fb_var.transp.offset);
+
+ /* Check bits per pixel */
+ if (tf->fb_var.bits_per_pixel != 32) {
+ SERROR("can't set fb bpp to 32");
+ return 0;
+ }
+
+ /* Set colormap */
+ cmap.start = 0;
+ cmap.len = 256;
+ cmap.red = tf->cmap[0];
+ cmap.green = tf->cmap[1];
+ cmap.blue = tf->cmap[2];
+ cmap.transp = NULL;
+ ioctl(tf->fb_fd, FBIOPUTCMAP, &cmap);
+
+ /* Get remaining settings */
+ ioctl(tf->fb_fd, FBIOGET_FSCREENINFO, &tf->fb_fix);
+
+ DEBUG(" line_lenght = %d\n", tf->fb_fix.line_length);
+
+ /* Map the fb */
+ off = (off_t)tf->fb_fix.smem_start & (pgsize - 1);
+ len = (size_t)tf->fb_fix.smem_len + off + (pgsize - 1);
+ len &= ~(pgsize - 1);
+ tf->fb_len = len;
+
+ tf->fb_base = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED,
+ tf->fb_fd, 0);
+ if (tf->fb_base == MAP_FAILED) {
+ SERROR("can't mmap framebuffer");
+ return 0;
+ }
+ tf->fb_ptr = tf->fb_base + off;
+
+ return 1;
+}
+
+static void twin_fbdev_switch(twin_fbdev_t *tf, int activate)
+{
+ tf->vt_active = activate;
+
+ DEBUG("console switch: %sactivating\n", activate ? "" : "de");
+
+ /* Upon activation */
+ if (activate) {
+ /* Switch complete */
+ ioctl(tf->vt_fd, VT_RELDISP, VT_ACKACQ);
+
+ /* Restore fbdev settings */
+ if (twin_fbdev_apply_config(tf)) {
+ /* Mark entire screen for refresh */
+ if (tf->screen)
+ twin_screen_damage (tf->screen, 0, 0,
+ tf->screen->width,
+ tf->screen->height);
+ tf->active = 1;
+ }
+ } else {
+ /* Allow switch. Maybe we want to expose some option
+ * to disallow them ?
+ */
+ ioctl(tf->vt_fd, VT_RELDISP, 1);
+
+ tf->active = 0;
+
+ if (tf->fb_base != MAP_FAILED)
+ munmap(tf->fb_base, tf->fb_len);
+ tf->fb_base = MAP_FAILED;
+ }
+}
+
+static twin_bool_t twin_fbdev_work(void *closure)
+{
+ twin_fbdev_t *tf = closure;
+
+ if (vt_switch_pending) {
+ twin_fbdev_switch(tf, !tf->vt_active);
+ vt_switch_pending = 0;
+ }
+
+ if (tf->screen && tf->active &&
+ twin_screen_damaged (tf->screen))
+ twin_screen_update(tf->screen);
+ return TWIN_TRUE;
+}
+
+static void twin_fbdev_vtswitch(int sig)
+{
+ signal(sig, twin_fbdev_vtswitch);
+ vt_switch_pending = 1;
+}
+
+static twin_bool_t twin_fbdev_read_events(int file, twin_file_op_t ops,
+ void *closure)
+{
+ twin_fbdev_t *tf = closure;
+ unsigned char events[16];
+ int i, count;
+
+ count = read(file, events, 16);
+
+ for (i = 0; i < count; i++) {
+ unsigned char e = events[i];
+
+ /* Handle special keys */
+ switch(e) {
+ case KEY_F1...KEY_F10:
+ ioctl(tf->vt_fd, VT_ACTIVATE, e - KEY_F1 + 1);
+ break;
+ case KEY_ESC:
+ kill(0, SIGINT);
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static twin_bool_t twin_fbdev_get_vt(twin_fbdev_t *tf, int wanted_vt)
+{
+ struct vt_stat vts;
+ int ttyfd;
+ char vtname[16];
+
+ /* Open tty0 and use it to look for a free vt */
+ ttyfd = open("/dev/tty0", O_WRONLY);
+ if (ttyfd < 0) {
+ SERROR("can't open /dev/tty0");
+ ttyfd = open("/dev/vc/0", O_WRONLY);
+ if (ttyfd < 0) {
+ SERROR("can't open /dev/vc/0");
+ return 0;
+ }
+ }
+ /* Get previous VT */
+ if (ioctl(ttyfd, VT_GETSTATE, &vts) < 0) {
+ SERROR("can't get current VT");
+ return 0;
+ }
+ tf->vt_prev = vts.v_active;
+
+ DEBUG("previous vt: %d\n", tf->vt_prev);
+
+ /* Sanity check wanted_vt and try to obtain a free VT. This is
+ * all somewhat racy but that's how everybody does it.
+ */
+ if (wanted_vt > 31)
+ wanted_vt = -1;
+ if (wanted_vt > 0 && (vts.v_state & (1 << wanted_vt))) {
+ IERROR("vt%d is busy\n", wanted_vt);
+ wanted_vt = -1;
+ }
+ if (wanted_vt < 0)
+ if (ioctl(ttyfd, VT_OPENQRY, &wanted_vt) < 0)
+ wanted_vt = -1;
+ if (wanted_vt < 0) {
+ IERROR("can't find a free VT");
+ return 0;
+ }
+
+ tf->vt_no = wanted_vt;
+
+ DEBUG("new vt: %d\n", tf->vt_no);
+
+
+ /* we don't need tty0 anymore, close it and open the target VT. */
+ close(ttyfd);
+
+ /* lose tty */
+ setpgrp();
+ if ((ttyfd = open("/dev/tty", O_RDWR)) >= 0) {
+ ioctl(ttyfd, TIOCNOTTY, 0);
+ close(ttyfd);
+ }
+
+ sprintf(vtname, "/dev/tty%d", tf->vt_no);
+ tf->vt_fd = open(vtname, O_RDWR | O_NONBLOCK);
+ if (tf->vt_fd < 0) {
+ sprintf(vtname, "/dev/vc/%d", tf->vt_no);
+ tf->vt_fd = open(vtname, O_RDWR | O_NONBLOCK);
+ }
+ if (tf->vt_fd < 0) {
+ SERROR("can't open tty %d", tf->vt_no);
+ return 0;
+ }
+
+ /* set new controlling terminal */
+ ioctl(tf->vt_fd, TIOCSCTTY, 1);
+
+ /* set keyboard mode */
+ ioctl(tf->vt_fd, KDSKBMODE, K_XLATE);
+
+ tf->vt_active = tf->active = 0;
+
+ return 1;
+}
+
+static twin_bool_t twin_fbdev_setup_vt(twin_fbdev_t *tf, int switch_sig)
+{
+ struct vt_mode vtm;
+ struct termios tio;
+
+ if (ioctl(tf->vt_fd, VT_GETMODE, &vtm) < 0) {
+ SERROR("can't get VT mode");
+ return 0;
+ }
+ vtm.mode = VT_PROCESS;
+ vtm.relsig = switch_sig;
+ vtm.acqsig = switch_sig;
+
+ signal(switch_sig, twin_fbdev_vtswitch);
+ tf->vt_swsig = switch_sig;
+
+ if (ioctl(tf->vt_fd, VT_SETMODE, &vtm) < 0) {
+ SERROR("can't set VT mode");
+ signal(switch_sig, SIG_IGN);
+ return 0;
+ }
+
+ tcgetattr(tf->vt_fd, &tf->old_tio);
+
+ ioctl(tf->vt_fd, KDGKBMODE, &tf->old_kbmode);
+ ioctl(tf->vt_fd, KDSKBMODE, K_MEDIUMRAW);
+
+ tio = tf->old_tio;
+ tio.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP);
+ tio.c_oflag = 0;
+ tio.c_cflag = CREAD | CS8;
+ tio.c_lflag = 0;
+ tio.c_cc[VTIME]=0;
+ tio.c_cc[VMIN]=1;
+ cfsetispeed(&tio, 9600);
+ cfsetospeed(&tio, 9600);
+ tcsetattr(tf->vt_fd, TCSANOW, &tio);
+
+ ioctl(tf->vt_fd, KDSETMODE, KD_GRAPHICS);
+
+ return 1;
+}
+
+static twin_bool_t twin_fbdev_init_fb(twin_fbdev_t *tf)
+{
+ int i;
+
+ /* We always open /dev/fb0 for now. Might want fixing */
+ tf->fb_fd = open("/dev/fb0", O_RDWR);
+ if (tf->fb_fd < 0) {
+ SERROR("can't open /dev/fb0");
+ return 0;
+ }
+
+ /* Get initial fbdev configuration */
+ if (ioctl(tf->fb_fd, FBIOGET_VSCREENINFO, &tf->fb_var) < 0) {
+ SERROR("can't get framebuffer config");
+ return 0;
+ }
+
+ DEBUG("initial screen size: %dx%d\n",
+ tf->fb_var.xres, tf->fb_var.yres);
+
+ tf->fb_base = MAP_FAILED;
+
+ for (i = 0; i < 256; i++) {
+ unsigned short c = (i << 8) | i;
+ tf->cmap[0][i] = tf->cmap[1][i] = tf->cmap[2][i] = c;
+ }
+
+ return 1;
+}
+
+static twin_bool_t twin_fbdev_init_screen(twin_fbdev_t *tf)
+{
+ tf->screen = twin_screen_create(tf->fb_var.xres,
+ tf->fb_var.yres,
+ NULL,
+ _twin_fbdev_put_span, tf);
+ if (tf->screen == NULL) {
+ IERROR("can't create twin screen");
+ return 0;
+ }
+
+ return 1;
+}
+
+static void twin_fbdev_cleanup_vt(twin_fbdev_t *tf)
+{
+ struct vt_mode vtm;
+
+ ioctl(tf->vt_fd, VT_GETMODE, &vtm);
+ vtm.mode = VT_AUTO;
+ vtm.relsig = 0;
+ vtm.acqsig = 0;
+ ioctl(tf->vt_fd, VT_SETMODE, &vtm);
+
+ signal(tf->vt_swsig, SIG_DFL);
+
+ tcsetattr(tf->vt_fd, TCSANOW, &tf->old_tio);
+ ioctl(tf->vt_fd, KDSKBMODE, tf->old_kbmode);
+
+ ioctl(tf->vt_fd, KDSETMODE, KD_TEXT);
+ ioctl(tf->vt_fd, VT_ACTIVATE, tf->vt_prev);
+ ioctl(tf->vt_fd, VT_WAITACTIVE, tf->vt_prev);
+}
+
+twin_fbdev_t *twin_fbdev_create(int wanted_vt, int switch_sig)
+{
+ twin_fbdev_t *tf;
+
+ if (twin_fb != NULL) {
+ IERROR("twin_fbdev supports only one instance");
+ return NULL;
+ }
+
+ tf = calloc(1, sizeof(twin_fbdev_t));
+ if (tf == NULL)
+ return NULL;
+
+ if (!twin_fbdev_get_vt(tf, wanted_vt))
+ goto err_free;
+
+ if (!twin_fbdev_setup_vt(tf, switch_sig))
+ goto err_release;
+
+ if (!twin_fbdev_init_fb(tf))
+ goto err_reset_vt;
+
+ if (!twin_fbdev_init_screen(tf))
+ goto err_close_fb;
+
+ twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, tf);
+
+ twin_set_file(twin_fbdev_read_events, tf->vt_fd, TWIN_READ, tf);
+
+ twin_fb = tf;
+ return tf;
+
+ err_close_fb:
+ close(tf->fb_fd);
+ err_reset_vt:
+ twin_fbdev_cleanup_vt(tf);
+ signal(switch_sig, SIG_DFL);
+ err_release:
+ close(tf->vt_fd);
+ err_free:
+ free(tf);
+ return NULL;
+}
+
+void twin_fbdev_destroy(twin_fbdev_t *tf)
+{
+ tf->active = 0;
+ twin_fbdev_cleanup_vt(tf);
+ close(tf->fb_fd);
+ close(tf->vt_fd);
+ free(tf);
+ twin_fb = NULL;
+}
+
+twin_bool_t twin_fbdev_activate(twin_fbdev_t *tf)
+{
+ /* If VT is not active, try to activate it. We don't deadlock
+ * here thanks to linux not waiting for VT_RELDISP on the target
+ * but we might want to be more careful
+ */
+ if (!tf->vt_active) {
+ if (ioctl(tf->vt_fd, VT_ACTIVATE, tf->vt_no) < 0)
+ return 0;
+ if (ioctl(tf->vt_fd, VT_WAITACTIVE, tf->vt_no) < 0)
+ return 0;
+ }
+
+ /* Run work to process the VT switch */
+ twin_fbdev_work(tf);
+
+ /* If the screen is not active, then we failed
+ * the fbdev configuration
+ */
+ return tf->active;
+}
+