Skip to content

Commit 500acd1

Browse files
committed
Support Linux framebuffer and input system
1 parent d36535c commit 500acd1

File tree

5 files changed

+471
-0
lines changed

5 files changed

+471
-0
lines changed

Makefile

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

100+
ifeq ($(CONFIG_BACKEND_FBDEV), y)
101+
BACKEND = fbdev
102+
libtwin.a_files-y += backend/fbdev.c
103+
libtwin.a_files-y += backend/linux_input.c
104+
endif
105+
100106
# Standalone application
101107

102108
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

backend/fbdev.c

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
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 <signal.h>
12+
#include <stdio.h>
13+
#include <stdlib.h>
14+
#include <sys/ioctl.h>
15+
#include <sys/mman.h>
16+
#include <twin.h>
17+
#include <unistd.h>
18+
19+
#include "linux_input.h"
20+
#include "twin_backend.h"
21+
22+
#define VT_MAX 63
23+
#define FBDEV_NAME "FRAMEBUFFER"
24+
#define FBDEV_DEFAULT "/dev/fb0"
25+
#define SCREEN(x) ((twin_context_t *) x)->screen
26+
#define PRIV(x) ((twin_fbdev_t *) ((twin_context_t *) x)->priv)
27+
28+
typedef struct {
29+
twin_screen_t *screen;
30+
31+
/* Linux input system */
32+
void *input;
33+
34+
/* Linux virtual terminal (VT) */
35+
int vt_fd;
36+
int vt_num;
37+
bool vt_active;
38+
39+
/* Linux framebuffer */
40+
int fb_fd;
41+
struct fb_var_screeninfo fb_var;
42+
struct fb_fix_screeninfo fb_fix;
43+
uint16_t cmap[3][256];
44+
uint8_t *fb_base;
45+
size_t fb_len;
46+
} twin_fbdev_t;
47+
48+
static void _twin_fbdev_put_span(twin_coord_t left,
49+
twin_coord_t top,
50+
twin_coord_t right,
51+
twin_argb32_t *pixels,
52+
void *closure)
53+
{
54+
twin_screen_t *screen = SCREEN(closure);
55+
twin_fbdev_t *tx = PRIV(closure);
56+
57+
if (tx->fb_base == MAP_FAILED)
58+
return;
59+
60+
twin_coord_t width = right - left;
61+
off_t off = top * screen->width + left;
62+
uint32_t *src = pixels;
63+
uint32_t *dest =
64+
(uint32_t *) ((uintptr_t) tx->fb_base + (off * sizeof(uint32_t)));
65+
memcpy(dest, src, width * sizeof(uint32_t));
66+
}
67+
68+
static void twin_fbdev_get_screen_size(twin_fbdev_t *tx,
69+
int *width,
70+
int *height)
71+
{
72+
struct fb_var_screeninfo info;
73+
ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &info);
74+
*width = info.xres;
75+
*height = info.yres;
76+
}
77+
78+
static void twin_fbdev_damage(twin_screen_t *screen, twin_fbdev_t *tx)
79+
{
80+
int width, height;
81+
twin_fbdev_get_screen_size(tx, &width, &height);
82+
twin_screen_damage(tx->screen, 0, 0, width, height);
83+
}
84+
85+
static bool twin_fbdev_work(void *closure)
86+
{
87+
twin_screen_t *screen = SCREEN(closure);
88+
89+
if (twin_screen_damaged(screen))
90+
twin_screen_update(screen);
91+
return true;
92+
}
93+
94+
static bool twin_fbdev_apply_config(twin_fbdev_t *tx)
95+
{
96+
/* Read changable information of the framebuffer */
97+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) == -1) {
98+
printf("error : failed getting framebuffer information\n");
99+
return false;
100+
}
101+
102+
/* Set the virtual screen size to be the same as the physical screen */
103+
tx->fb_var.xres_virtual = tx->fb_var.xres;
104+
tx->fb_var.yres_virtual = tx->fb_var.yres;
105+
tx->fb_var.bits_per_pixel = 32;
106+
if (ioctl(tx->fb_fd, FBIOPUT_VSCREENINFO, &tx->fb_var) < 0) {
107+
printf("Failed to set framebuffer mode\n");
108+
return false;
109+
}
110+
111+
/* Read changable information of the framebuffer again */
112+
if (ioctl(tx->fb_fd, FBIOGET_VSCREENINFO, &tx->fb_var) < 0) {
113+
printf("Failed to get framebuffer information\n");
114+
return false;
115+
}
116+
117+
/* Check bits per pixel */
118+
if (tx->fb_var.bits_per_pixel != 32) {
119+
printf("Failed to set framebuffer bpp to 32\n");
120+
return false;
121+
}
122+
123+
/* Read unchangable information of the framebuffer */
124+
ioctl(tx->fb_fd, FBIOGET_FSCREENINFO, &tx->fb_fix);
125+
126+
/* Align the framebuffer memory address with the page size */
127+
off_t pgsize = getpagesize();
128+
off_t start = (off_t) tx->fb_fix.smem_start & (pgsize - 1);
129+
130+
/* Round up the framebuffer memory size to match the page size */
131+
tx->fb_len = start + (size_t) tx->fb_fix.smem_len + (pgsize - 1);
132+
tx->fb_len &= ~(pgsize - 1);
133+
134+
/* Map framebuffer device to the virtual memory */
135+
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
136+
tx->fb_fd, 0);
137+
if (tx->fb_base == MAP_FAILED) {
138+
printf("Failed to mmap framebuffer.\n");
139+
return false;
140+
}
141+
142+
return true;
143+
}
144+
145+
static bool twin_find_free_vt(int *vt_num)
146+
{
147+
char vt_path[30];
148+
int fd;
149+
150+
/* Find a usable virtual terminal. Since VT1 is occupied by the GNU/Linux
151+
* desktop environment and VT2 is unusable, the search begins from VT3.
152+
*/
153+
for (int i = 3; i <= VT_MAX; i++) {
154+
snprintf(vt_path, sizeof(vt_path), "/dev/tty%d", i);
155+
fd = open(vt_path, O_RDWR);
156+
if (fd > 0) {
157+
close(fd);
158+
*vt_num = i;
159+
return true;
160+
}
161+
}
162+
163+
/* No free VT found */
164+
return false;
165+
}
166+
167+
static int twin_vt_open(int vt_num)
168+
{
169+
int fd;
170+
171+
char vt_dev[30] = {0};
172+
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);
173+
174+
fd = open(vt_dev, O_RDWR);
175+
if (fd < 0) {
176+
printf("Failed to open %s.\n", vt_dev);
177+
}
178+
179+
return fd;
180+
}
181+
182+
static bool twin_vt_setup(twin_fbdev_t *tx)
183+
{
184+
/* Open VT0 to inquire information */
185+
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
186+
return false;
187+
}
188+
189+
/* Find next free virtual terminal */
190+
if (!twin_find_free_vt(&tx->vt_num)) {
191+
printf("Failed to find a free virtual terminal.\n");
192+
return false;
193+
}
194+
close(tx->vt_fd);
195+
196+
/* Open the found free virtual terminal */
197+
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
198+
return false;
199+
}
200+
201+
/* Switch to the free virtual terminal */
202+
if (ioctl(tx->vt_fd, VT_ACTIVATE, tx->vt_num) < 0) {
203+
printf("Failed to activate virtual terminal %d.\n", tx->vt_num);
204+
return false;
205+
}
206+
207+
/* This brings Mado to the activated virtual terminal */
208+
if (ioctl(tx->vt_fd, VT_WAITACTIVE, tx->vt_num) < 0) {
209+
printf("Failed to wait virtual terminal %d to be activated.\n",
210+
tx->vt_num);
211+
return false;
212+
}
213+
214+
/* Disable virtual terminal output */
215+
/* TODO: Warning: There is currently no way to stop the program once
216+
* KD_GRAPHICS mode is set. To prevent the system from rebooting, consider
217+
* commenting out the following code.
218+
*/
219+
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
220+
printf("Failed to set KD_GRAPHICS mode.\n");
221+
return false;
222+
}
223+
224+
return true;
225+
}
226+
227+
twin_context_t *twin_fbdev_init(int width, int height)
228+
{
229+
char *fbdev_path = getenv(FBDEV_NAME);
230+
if (!fbdev_path) {
231+
printf(
232+
"Environment variable $FRAMEBUFFER not set, use %s by default.\n",
233+
FBDEV_DEFAULT);
234+
fbdev_path = FBDEV_DEFAULT;
235+
}
236+
237+
twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
238+
if (!ctx)
239+
return NULL;
240+
ctx->priv = calloc(1, sizeof(twin_fbdev_t));
241+
if (!ctx->priv)
242+
return NULL;
243+
244+
twin_fbdev_t *tx = ctx->priv;
245+
246+
/* Open the framebuffer device */
247+
tx->fb_fd = open(fbdev_path, O_RDWR);
248+
if (tx->fb_fd == -1) {
249+
printf("Failed to open %s.\n", fbdev_path);
250+
goto bail;
251+
}
252+
253+
/* Set up virtual terminal environment */
254+
if (!twin_vt_setup(tx)) {
255+
goto bail_fb_fd;
256+
}
257+
258+
/* Apply configurations to the framebuffer device */
259+
if (!twin_fbdev_apply_config(tx)) {
260+
printf("Failed to apply configurations to the framebuffer device.\n");
261+
goto bail_vt_fd;
262+
}
263+
264+
/* Create TWIN screen */
265+
ctx->screen =
266+
twin_screen_create(width, height, NULL, _twin_fbdev_put_span, ctx);
267+
268+
/* Create Linux input system object */
269+
tx->input = twin_linux_input_create(ctx->screen);
270+
if (!tx->input) {
271+
printf("Failed at creating Linux input system object.\n");
272+
goto bail_vt_fd;
273+
}
274+
275+
/* Setup file handler and work functions */
276+
twin_set_work(twin_fbdev_work, TWIN_WORK_REDISPLAY, ctx);
277+
278+
return ctx;
279+
280+
bail_vt_fd:
281+
close(tx->vt_fd);
282+
bail_fb_fd:
283+
close(tx->fb_fd);
284+
bail:
285+
free(ctx->priv);
286+
free(ctx);
287+
return NULL;
288+
}
289+
290+
static void twin_fbdev_configure(twin_context_t *ctx)
291+
{
292+
int width, height;
293+
twin_fbdev_t *tx = ctx->priv;
294+
twin_fbdev_get_screen_size(tx, &width, &height);
295+
twin_screen_resize(ctx->screen, width, height);
296+
}
297+
298+
static void twin_fbdev_exit(twin_context_t *ctx)
299+
{
300+
if (!ctx)
301+
return;
302+
303+
twin_fbdev_t *tx = PRIV(ctx);
304+
/* TODO: KD_TEXT mode setting requires keyboard input handling
305+
* or a dedicated button on the UI
306+
*/
307+
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
308+
munmap(tx->fb_base, tx->fb_len);
309+
twin_linux_input_destroy(tx->input);
310+
close(tx->vt_fd);
311+
close(tx->fb_fd);
312+
free(ctx->priv);
313+
free(ctx);
314+
}
315+
316+
/* Register the Linux framebuffer backend */
317+
318+
const twin_backend_t g_twin_backend = {
319+
.init = twin_fbdev_init,
320+
.configure = twin_fbdev_configure,
321+
.exit = twin_fbdev_exit,
322+
};

0 commit comments

Comments
 (0)