blob: 921c2e66c42006f4225b3eb157df5589b111edc5 [file] [log] [blame]
/*
* 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"
#include "twinint.h"
#ifdef HAVE_ALTIVEC
#include <altivec.h>
#endif
#define _IMMEDIATE_REFRESH
/* 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++);
}
#ifdef HAVE_ALTIVEC
static void _twin_fbdev_vec_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;
vector unsigned char edgeperm;
vector unsigned char src0v, src1v, srcv;
if (!tf->active || tf->fb_base == MAP_FAILED)
return;
dest = (unsigned int *)(tf->fb_ptr + top * tf->fb_fix.line_length);
dest += left;
while((((unsigned long)dest) & 0xf) && width--)
*(dest++) = *(pixels++);
edgeperm = vec_lvsl (0, pixels);
src0v = vec_ld (0, pixels);
while(width >= 4) {
src1v = vec_ld (16, pixels);
srcv = vec_perm (src0v, src1v, edgeperm);
vec_st ((vector unsigned int)srcv, 0, dest);
src0v = src1v;
dest += 4;
pixels += 4;
width -= 4;
}
while(width--)
*(dest++) = *(pixels++);
}
#endif /* HAVE_ALTIVEC */
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)) {
tf->active = 1;
/* Mark entire screen for refresh */
if (tf->screen)
twin_screen_damage (tf->screen, 0, 0,
tf->screen->width,
tf->screen->height);
}
} 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;
}
#ifndef _IMMEDIATE_REFRESH
if (tf->screen && tf->active &&
twin_screen_damaged (tf->screen))
twin_screen_update(tf->screen);
#endif /* _IMMEDIATE_REFRESH */
return TWIN_TRUE;
}
#ifdef _IMMEDIATE_REFRESH
static void twin_fbdev_damaged(void *closure)
{
twin_fbdev_t *tf = closure;
#if 0
DEBUG("fbdev damaged %d,%d,%d,%d, active=%d\n",
tf->screen->damage.left, tf->screen->damage.top,
tf->screen->damage.right, tf->screen->damage.bottom,
tf->active);
#endif
if (tf->active && twin_screen_damaged (tf->screen))
twin_screen_update(tf->screen);
}
#endif /* _IMMEDIATE_REFRESH */
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 maybe_unused ops,
void *closure)
{
twin_fbdev_t *tf = closure;
unsigned char events[16];
twin_event_t evt;
int i, count, down;
count = read(file, events, 16);
for (i = 0; i < count; i++) {
unsigned char e = events[i];
down = !(e & 0x80);
e &= 0x7f;
/* XXX Handle special keys (make more configurable) */
switch(e) {
case KEY_F1...KEY_F10:
if (down)
ioctl(tf->vt_fd, VT_ACTIVATE, e - KEY_F1 + 1);
break;
case KEY_ESC:
if (down)
kill(0, SIGINT);
break;
default:
evt.kind = down ? TwinEventKeyDown : TwinEventKeyUp;
evt.u.key.key = e;
twin_screen_dispatch(tf->screen, &evt);
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)
{
twin_put_span_t span;
span = _twin_fbdev_put_span;
#ifdef HAVE_ALTIVEC
if (twin_has_feature(TWIN_FEATURE_ALTIVEC))
span = _twin_fbdev_vec_put_span;
#endif
tf->screen = twin_screen_create(tf->fb_var.xres,
tf->fb_var.yres,
NULL, 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_ext(int wanted_vt, int switch_sig,
int handle_events)
{
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);
if (handle_events)
twin_set_file(twin_fbdev_read_events, tf->vt_fd, TWIN_READ, tf);
#ifdef _IMMEDIATE_REFRESH
twin_screen_register_damaged(tf->screen, twin_fbdev_damaged, tf);
#endif
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;
}
twin_bool_t
twin_fbdev_process_events (twin_fbdev_t *tf)
{
twin_bool_t result;
_twin_run_work();
result = twin_fbdev_read_events(tf->vt_fd, 0, tf);
_twin_run_work();
return result;
}