-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support Linux framebuffer and input system
- Loading branch information
1 parent
d36535c
commit 89be70c
Showing
5 changed files
with
464 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,315 @@ | ||
/* | ||
* 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" | ||
|
||
#define VT_MAX 63 | ||
#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) { | ||
printf("error : failed getting framebuffer information\n"); | ||
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) { | ||
printf("Failed to set framebuffer mode\n"); | ||
return false; | ||
} | ||
|
||
/* Read changable information of the framebuffer again */ | ||
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) { | ||
printf("Failed to get framebuffer information\n"); | ||
return false; | ||
} | ||
|
||
/* Check bits per pixel */ | ||
if (tx->fb_var.bits_per_pixel != 32) { | ||
printf("Failed to set framebuffer bpp to 32\n"); | ||
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) { | ||
printf("Failed to mmap framebuffer.\n"); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
static bool twin_find_free_vt(int *vt_num) | ||
{ | ||
char vt_path[30]; | ||
int fd; | ||
|
||
/* Find a usable virtual terminal. Since VT1 is occupied by the GNU/Linux | ||
* desktop environment and VT2 is unusable, the search begins from VT3. | ||
*/ | ||
for (int i = 3; i <= VT_MAX; i++) { | ||
snprintf(vt_path, sizeof(vt_path), "/dev/tty%d", i); | ||
fd = open(vt_path, O_RDWR); | ||
if (fd > 0) { | ||
close(fd); | ||
*vt_num = i; | ||
return true; | ||
} | ||
} | ||
|
||
/* No free VT found */ | ||
return false; | ||
} | ||
|
||
static bool twin_vt_setup(twin_fbdev_t *tx) | ||
{ | ||
/* Open VT0 to inquire information */ | ||
tx->vt_fd = open("/dev/tty0", O_RDWR); | ||
if (tx->vt_fd == -1) { | ||
printf("Failed to open /dev/tty0.\n"); | ||
return false; | ||
} | ||
|
||
/* Find next free virtual terminal */ | ||
if (!twin_find_free_vt(&tx->vt_num)) { | ||
printf("Failed to find a free virtual terminal.\n"); | ||
return false; | ||
} | ||
close(tx->vt_fd); | ||
|
||
/* Open the found free virtual terminal */ | ||
char vt_dev[30] = {0}; | ||
snprintf(vt_dev, 30, "/dev/tty%d", tx->vt_num); | ||
close(tx->vt_fd); | ||
|
||
tx->vt_fd = open(vt_dev, O_RDWR); | ||
if (tx->vt_fd == -1) { | ||
printf("Failed to open %s.\n", vt_dev); | ||
return false; | ||
} | ||
|
||
/* Switch to the free virtual terminal */ | ||
if (ioctl(tx->vt_fd, VT_ACTIVATE, tx->vt_num) < 0) { | ||
printf("Failed to activate virtual terminal %d.\n", tx->vt_num); | ||
return false; | ||
} | ||
|
||
/* This brings Mado to the activated virtual terminal */ | ||
if (ioctl(tx->vt_fd, VT_WAITACTIVE, tx->vt_num) < 0) { | ||
printf("Failed to wait virtual terminal %d to be activated.\n", | ||
tx->vt_num); | ||
return false; | ||
} | ||
|
||
/* Disable virtual terminal output */ | ||
/* TODO: Warning: There is currently no way to stop the program once | ||
* KD_GRAPHICS mode is set. To prevent the system from rebooting, consider | ||
* commenting out the following code. | ||
*/ | ||
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) { | ||
printf("Failed to set KD_GRAPHICS mode.\n"); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
twin_context_t *twin_fbdev_init(int width, int height) | ||
{ | ||
char *fbdev_path = getenv(FBDEV_NAME); | ||
if (!fbdev_path) { | ||
printf( | ||
"Environment variable $FRAMEBUFFER not set, use %s by default.\n", | ||
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) { | ||
printf("Failed to open %s.\n", 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)) { | ||
printf("Failed to apply configurations to the framebuffer device.\n"); | ||
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) { | ||
printf("Failed at creating Linux input system object.\n"); | ||
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); | ||
/* TODO: KD_TEXT mode setting requires keyboard input handling | ||
* or a dedicated button on the UI | ||
*/ | ||
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, | ||
}; |
Oops, something went wrong.