Skip to content

Commit 0ee09ca

Browse files
committed
Support Linux framebuffer and input system
Though TWIN (and by extension, Mado) was designed to operate in single-threaded environments, the Linux framebuffer backend in Mado currently uses POSIX thread to manage input system events. Coroutines could potentially be used as an alternative.
1 parent ceb748f commit 0ee09ca

File tree

5 files changed

+486
-0
lines changed

5 files changed

+486
-0
lines changed

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ libtwin.a_cflags-y += $(shell sdl2-config --cflags)
104104
TARGET_LIBS += $(shell sdl2-config --libs)
105105
endif
106106

107+
ifeq ($(CONFIG_BACKEND_FBDEV), y)
108+
BACKEND = fbdev
109+
libtwin.a_files-y += backend/fbdev.c
110+
libtwin.a_files-y += backend/linux_input.c
111+
endif
112+
107113
# Standalone application
108114

109115
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

backend/fbdev.c

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* Twin - A Tiny Window System
3+
* Copyright (c) 2024 National Cheng Kung University, Taiwan
4+
* All rights reserved.
5+
*/
6+
7+
#include <fcntl.h>
8+
#include <linux/fb.h>
9+
#include <linux/kd.h>
10+
#include <linux/vt.h>
11+
#include <stdlib.h>
12+
#include <sys/ioctl.h>
13+
#include <sys/mman.h>
14+
#include <twin.h>
15+
#include <unistd.h>
16+
17+
#include "linux_input.h"
18+
#include "twin_backend.h"
19+
#include "twin_private.h"
20+
21+
#define FBDEV_NAME "FRAMEBUFFER"
22+
#define FBDEV_DEFAULT "/dev/fb0"
23+
#define SCREEN(x) ((twin_context_t *) x)->screen
24+
#define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv)
25+
26+
typedef struct {
27+
twin_screen_t *screen;
28+
29+
/* Linux input system */
30+
void *input;
31+
32+
/* Linux virtual terminal (VT) */
33+
int vt_fd;
34+
int vt_num;
35+
bool vt_active;
36+
37+
/* Linux framebuffer */
38+
int fb_fd;
39+
struct fb_var_screeninfo fb_var;
40+
struct fb_fix_screeninfo fb_fix;
41+
uint16_t cmap[3][256];
42+
uint8_t *fb_base;
43+
size_t fb_len;
44+
} twin_fbdev_t;
45+
46+
static void _twin_fbdev_put_span(twin_coord_t left,
47+
twin_coord_t top,
48+
twin_coord_t right,
49+
twin_argb32_t *pixels,
50+
void *closure)
51+
{
52+
twin_screen_t *screen = SCREEN(closure);
53+
twin_fbdev_t *tx = PRIV(closure);
54+
55+
if (tx->fb_base == MAP_FAILED)
56+
return;
57+
58+
twin_coord_t width = right - left;
59+
off_t off = top * screen->width + left;
60+
uint32_t *src = pixels;
61+
uint32_t *dest =
62+
(uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t)));
63+
memcpy(dest, src, width * sizeof(uint32_t));
64+
}
65+
66+
static void twin_fbdev_get_screen_size(twin_fbdev_t *tx,
67+
int *width,
68+
int *height)
69+
{
70+
struct fb_var_screeninfo info;
71+
ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &info);
72+
*width = info.xres;
73+
*height = info.yres;
74+
}
75+
76+
static void twin_fbdev_damage(twin_screen_t *screen, twin_fbdev_t *tx)
77+
{
78+
int width, height;
79+
twin_fbdev_get_screen_size(tx, &width, &height);
80+
twin_screen_damage(tx->screen, 0, 0, width, height);
81+
}
82+
83+
static bool twin_fbdev_work(void *closure)
84+
{
85+
twin_screen_t *screen = SCREEN(closure);
86+
87+
if (twin_screen_damaged(screen))
88+
twin_screen_update(screen);
89+
return true;
90+
}
91+
92+
static bool twin_fbdev_apply_config(twin_fbdev_t *tx)
93+
{
94+
/* Read changable information of the framebuffer */
95+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) == -1) {
96+
log_error("Failed to get framebuffer information");
97+
return false;
98+
}
99+
100+
/* Set the virtual screen size to be the same as the physical screen */
101+
tx->fb_var.xres_virtual = tx->fb_var.xres;
102+
tx->fb_var.yres_virtual = tx->fb_var.yres;
103+
tx->fb_var.bits_per_pixel = 32;
104+
if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) {
105+
log_error("Failed to set framebuffer mode");
106+
return false;
107+
}
108+
109+
/* Read changable information of the framebuffer again */
110+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) {
111+
log_error("Failed to get framebuffer information");
112+
return false;
113+
}
114+
115+
/* Check bits per pixel */
116+
if (tx->fb_var.bits_per_pixel != 32) {
117+
log_error("Failed to set framebuffer bpp to 32");
118+
return false;
119+
}
120+
121+
/* Read unchangable information of the framebuffer */
122+
ioctl(tx->fb_fd, FBIOGET_FSCREENINFO, &tx->fb_fix);
123+
124+
/* Align the framebuffer memory address with the page size */
125+
off_t pgsize = getpagesize();
126+
off_t start = (off_t) tx->fb_fix.smem_start & (pgsize - 1);
127+
128+
/* Round up the framebuffer memory size to match the page size */
129+
tx->fb_len = start + (size_t) tx->fb_fix.smem_len + (pgsize - 1);
130+
tx->fb_len &= ~(pgsize - 1);
131+
132+
/* Map framebuffer device to the virtual memory */
133+
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
134+
tx->fb_fd, 0);
135+
if (tx->fb_base == MAP_FAILED) {
136+
log_error("Failed to mmap framebuffer");
137+
return false;
138+
}
139+
140+
return true;
141+
}
142+
143+
static int twin_vt_open(int vt_num)
144+
{
145+
int fd;
146+
147+
char vt_dev[30] = {0};
148+
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);
149+
150+
fd = open(vt_dev, O_RDWR);
151+
if (fd < 0) {
152+
log_error("Failed to open %s", vt_dev);
153+
}
154+
155+
return fd;
156+
}
157+
158+
static bool twin_vt_setup(twin_fbdev_t *tx)
159+
{
160+
/* Open VT0 to inquire information */
161+
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
162+
return false;
163+
}
164+
165+
/* Inquire for current VT number */
166+
struct vt_stat vt;
167+
if (ioctl(tx->vt_fd, VT_GETSTATE, &vt) == -1) {
168+
log_error("Failed to get VT number");
169+
return false;
170+
}
171+
tx->vt_num = vt.v_active;
172+
173+
/* Open the VT */
174+
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
175+
return false;
176+
}
177+
178+
/* Set VT to graphics mode to inhibit command-line text */
179+
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
180+
log_error("Failed to set KD_GRAPHICS mode");
181+
return false;
182+
}
183+
184+
return true;
185+
}
186+
187+
twin_context_t *twin_fbdev_init(int width, int height)
188+
{
189+
char *fbdev_path = getenv(FBDEV_NAME);
190+
if (!fbdev_path) {
191+
log_info("Environment variable $FRAMEBUFFER not set, use %s by default",
192+
FBDEV_DEFAULT);
193+
fbdev_path = FBDEV_DEFAULT;
194+
}
195+
196+
twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
197+
if (!ctx)
198+
return NULL;
199+
ctx->priv = calloc(1, sizeof(twin_fbdev_t));
200+
if (!ctx->priv)
201+
return NULL;
202+
203+
twin_fbdev_t *tx = ctx->priv;
204+
205+
/* Open the framebuffer device */
206+
tx->fb_fd = open(fbdev_path, O_RDWR);
207+
if (tx->fb_fd == -1) {
208+
log_error("Failed to open %s", fbdev_path);
209+
goto bail;
210+
}
211+
212+
/* Set up virtual terminal environment */
213+
if (!twin_vt_setup(tx)) {
214+
goto bail_fb_fd;
215+
}
216+
217+
/* Apply configurations to the framebuffer device */
218+
if (!twin_fbdev_apply_config(tx)) {
219+
log_error("Failed to apply configurations to the framebuffer device");
220+
goto bail_vt_fd;
221+
}
222+
223+
/* Create TWIN screen */
224+
ctx->screen =
225+
twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx);
226+
227+
/* Create Linux input system object */
228+
tx->input = twin_linux_input_create(ctx->screen);
229+
if (!tx->input) {
230+
log_error("Failed to create Linux input system object");
231+
goto bail_vt_fd;
232+
}
233+
234+
/* Setup file handler and work functions */
235+
twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, ctx);
236+
237+
return ctx;
238+
239+
bail_vt_fd:
240+
close(tx->vt_fd);
241+
bail_fb_fd:
242+
close(tx->fb_fd);
243+
bail:
244+
free(ctx->priv);
245+
free(ctx);
246+
return NULL;
247+
}
248+
249+
static void twin_fbdev_configure(twin_context_t *ctx)
250+
{
251+
int width, height;
252+
twin_fbdev_t *tx = ctx->priv;
253+
twin_fbdev_get_screen_size(tx, &width, &height);
254+
twin_screen_resize(ctx->screen, width, height);
255+
}
256+
257+
static void twin_fbdev_exit(twin_context_t *ctx)
258+
{
259+
if (!ctx)
260+
return;
261+
262+
twin_fbdev_t *tx = PRIV(ctx);
263+
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
264+
munmap(tx->fb_base, tx->fb_len);
265+
twin_linux_input_destroy(tx->input);
266+
close(tx->vt_fd);
267+
close(tx->fb_fd);
268+
free(ctx->priv);
269+
free(ctx);
270+
}
271+
272+
/* Register the Linux framebuffer backend */
273+
274+
const twin_backend_t g_twin_backend = {
275+
.init = twin_fbdev_init,
276+
.configure = twin_fbdev_configure,
277+
.exit = twin_fbdev_exit,
278+
};

0 commit comments

Comments
 (0)