Skip to content

Commit

Permalink
Support Linux framebuffer and input system
Browse files Browse the repository at this point in the history
  • Loading branch information
shengwen-tw committed Sep 16, 2024
1 parent ceb748f commit 0b6d133
Show file tree
Hide file tree
Showing 5 changed files with 488 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ libtwin.a_cflags-y += $(shell sdl2-config --cflags)
TARGET_LIBS += $(shell sdl2-config --libs)
endif

ifeq ($(CONFIG_BACKEND_FBDEV), y)
BACKEND = fbdev
libtwin.a_files-y += backend/fbdev.c
libtwin.a_files-y += backend/linux_input.c
endif

# Standalone application

ifeq ($(CONFIG_DEMO_APPLICATIONS), y)
Expand Down
280 changes: 280 additions & 0 deletions backend/fbdev.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* Twin - A Tiny Window System
* Copyright (c) 2024 National Cheng Kung University, Taiwan
* All rights reserved.
*/

#include <fcntl.h>
#include <linux/fb.h>
#include <linux/kd.h>
#include <linux/vt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <twin.h>
#include <unistd.h>

#include "linux_input.h"
#include "twin_backend.h"
#include "twin_private.h"

#define FBDEV_NAME "FRAMEBUFFER"
#define FBDEV_DEFAULT "/dev/fb0"
#define SCREEN(x) ((twin_context_t *) x)->screen
#define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv)

typedef struct {
twin_screen_t *screen;

/* Linux input system */
void *input;

/* Linux virtual terminal (VT) */
int vt_fd;
int vt_num;
bool vt_active;

/* Linux framebuffer */
int fb_fd;
struct fb_var_screeninfo fb_var;
struct fb_fix_screeninfo fb_fix;
uint16_t cmap[3][256];
uint8_t *fb_base;
size_t fb_len;
} twin_fbdev_t;

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_screen_t *screen = SCREEN(closure);
twin_fbdev_t *tx = PRIV(closure);

if (tx->fb_base == MAP_FAILED)
return;

twin_coord_t width = right - left;
off_t off = top * screen->width + left;
uint32_t *src = pixels;
uint32_t *dest =
(uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t)));
memcpy(dest, src, width * sizeof(uint32_t));
}

static void twin_fbdev_get_screen_size(twin_fbdev_t *tx,
int *width,
int *height)
{
struct fb_var_screeninfo info;
ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &info);
*width = info.xres;
*height = info.yres;
}

static void twin_fbdev_damage(twin_screen_t *screen, twin_fbdev_t *tx)
{
int width, height;
twin_fbdev_get_screen_size(tx, &width, &height);
twin_screen_damage(tx->screen, 0, 0, width, height);
}

static bool twin_fbdev_work(void *closure)
{
twin_screen_t *screen = SCREEN(closure);

if (twin_screen_damaged(screen))
twin_screen_update(screen);
return true;
}

static bool twin_fbdev_apply_config(twin_fbdev_t *tx)
{
/* Read changable information of the framebuffer */
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) == -1) {
log_error("Failed to get framebuffer information");
return false;
}

/* Set the virtual screen size to be the same as the physical screen */
tx->fb_var.xres_virtual = tx->fb_var.xres;
tx->fb_var.yres_virtual = tx->fb_var.yres;
tx->fb_var.bits_per_pixel = 32;
if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) {
log_error("Failed to set framebuffer mode");
return false;
}

/* Read changable information of the framebuffer again */
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) {
log_error("Failed to get framebuffer information");
return false;
}

/* Check bits per pixel */
if (tx->fb_var.bits_per_pixel != 32) {
log_error("Failed to set framebuffer bpp to 32");
return false;
}

/* Read unchangable information of the framebuffer */
ioctl(tx->fb_fd, FBIOGET_FSCREENINFO, &tx->fb_fix);

/* Align the framebuffer memory address with the page size */
off_t pgsize = getpagesize();
off_t start = (off_t) tx->fb_fix.smem_start & (pgsize - 1);

/* Round up the framebuffer memory size to match the page size */
tx->fb_len = start + (size_t) tx->fb_fix.smem_len + (pgsize - 1);
tx->fb_len &= ~(pgsize - 1);

/* Map framebuffer device to the virtual memory */
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
tx->fb_fd, 0);
if (tx->fb_base == MAP_FAILED) {
log_error("Failed to mmap framebuffer");
return false;
}

return true;
}

static int twin_vt_open(int vt_num)
{
int fd;

char vt_dev[30] = {0};
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);

fd = open(vt_dev, O_RDWR);
if (fd < 0) {
log_error("Failed to open %s", vt_dev);
}

return fd;
}

static bool twin_vt_setup(twin_fbdev_t *tx)
{
/* Open VT0 to inquire information */
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
return false;
}

/* Inquire for current VT number */
struct vt_stat vt;
if (ioctl(tx->vt_fd, VT_GETSTATE, &vt) == -1) {
log_error("Failed to get VT number");
return false;
}
tx->vt_num = vt.v_active;

/* Open the VT */
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
return false;
}

/* Set VT to graphics mode to inhibit command-line text */
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
log_error("Failed to set KD_GRAPHICS mode");
return false;
}

return true;
}

twin_context_t *twin_fbdev_init(int width, int height)
{
char *fbdev_path = getenv(FBDEV_NAME);
if (!fbdev_path) {
log_info("Environment variable $FRAMEBUFFER not set, use %s by default",
FBDEV_DEFAULT);
fbdev_path = FBDEV_DEFAULT;
}

twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
if (!ctx)
return NULL;
ctx->priv = calloc(1, sizeof(twin_fbdev_t));
if (!ctx->priv)
return NULL;

twin_fbdev_t *tx = ctx->priv;

/* Open the framebuffer device */
tx->fb_fd = open(fbdev_path, O_RDWR);
if (tx->fb_fd == -1) {
log_error("Failed to open %s", fbdev_path);
goto bail;
}

/* Set up virtual terminal environment */
if (!twin_vt_setup(tx)) {
goto bail_fb_fd;
}

/* Apply configurations to the framebuffer device */
if (!twin_fbdev_apply_config(tx)) {
log_error("Failed to apply configurations to the framebuffer device");
goto bail_vt_fd;
}

/* Create TWIN screen */
ctx->screen =
twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx);

/* Create Linux input system object */
tx->input = twin_linux_input_create(ctx->screen);
if (!tx->input) {
log_error("Failed to create Linux input system object");
goto bail_vt_fd;
}

/* Setup file handler and work functions */
twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, ctx);

return ctx;

bail_vt_fd:
close(tx->vt_fd);
bail_fb_fd:
close(tx->fb_fd);
bail:
free(ctx->priv);
free(ctx);
return NULL;
}

static void twin_fbdev_configure(twin_context_t *ctx)
{
int width, height;
twin_fbdev_t *tx = ctx->priv;
twin_fbdev_get_screen_size(tx, &width, &height);
twin_screen_resize(ctx->screen, width, height);
}

static void twin_fbdev_exit(twin_context_t *ctx)
{
if (!ctx)
return;

twin_fbdev_t *tx = PRIV(ctx);
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
munmap(tx->fb_base, tx->fb_len);
twin_linux_input_destroy(tx->input);
close(tx->vt_fd);
close(tx->fb_fd);
free(ctx->priv);
free(ctx);
}

/* Register the Linux framebuffer backend */

const twin_backend_t g_twin_backend = {
.init = twin_fbdev_init,
.configure = twin_fbdev_configure,
.exit = twin_fbdev_exit,
};
Loading

0 comments on commit 0b6d133

Please sign in to comment.