Skip to content

Commit f8729f4

Browse files
committed
Support Linux DRM backend
Implemented DRM-based framebuffer setup, including: - Opening DRM device - Creating the dumb buffer and mapping it to framebuffer - Setting mode and handling CRTC for connected display output Currently, using the DRM legacy interface is suitable for Mado's backend if we only need basic window display without advanced features. The DRM legacy is simpler to implement. Close #60
1 parent b75d5ca commit f8729f4

File tree

3 files changed

+357
-0
lines changed

3 files changed

+357
-0
lines changed

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ libtwin.a_files-y += backend/fbdev.c
110110
libtwin.a_files-y += backend/linux_input.c
111111
endif
112112

113+
ifeq ($(CONFIG_BACKEND_DRM), y)
114+
BACKEND = drm
115+
libtwin.a_files-y += backend/drm.c
116+
libtwin.a_files-y += backend/linux_input.c
117+
libtwin.a_cflags-y += $(shell pkg-config --cflags libdrm)
118+
TARGET_LIBS += $(shell pkg-config --libs libdrm)
119+
endif
120+
113121
# Standalone application
114122

115123
ifeq ($(CONFIG_DEMO_APPLICATIONS), y)

backend/drm.c

+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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 <libdrm/drm.h>
9+
#include <libdrm/drm_mode.h>
10+
#include <linux/kd.h>
11+
#include <linux/vt.h>
12+
#include <stdlib.h>
13+
#include <sys/ioctl.h>
14+
#include <sys/mman.h>
15+
#include <twin.h>
16+
#include <xf86drm.h>
17+
#include <xf86drmMode.h>
18+
19+
#include "linux_input.h"
20+
#include "twin_backend.h"
21+
22+
#define DRM_DRI_NAME "DRI_CARD"
23+
#define DRM_DRI_DEFAULT "/dev/dri/card0"
24+
#define SCREEN(x) ((twin_context_t *) x)->screen
25+
#define RES(x) ((twin_drm_t *) x)->res
26+
#define CONN(x) ((twin_drm_t *) x)->conn
27+
#define CRTC(x) ((twin_drm_t *) x)->crtc
28+
#define PRIV(x) ((twin_drm_t *) ((twin_context_t *) x)->priv)
29+
30+
typedef struct {
31+
twin_screen_t *screen;
32+
33+
/* Linux input system */
34+
void *input;
35+
36+
/* Linux virtual terminal (VT) */
37+
int vt_fd;
38+
int vt_num;
39+
bool vt_active;
40+
41+
/* DRM driver */
42+
int width, height;
43+
int drm_dri_fd;
44+
drmModeResPtr res;
45+
drmModeConnectorPtr conn;
46+
drmModeCrtcPtr crtc;
47+
uint32_t fb_id;
48+
uint32_t *fb_base;
49+
size_t fb_len;
50+
// int display_width;
51+
} twin_drm_t;
52+
53+
static void _twin_drm_put_span(twin_coord_t left,
54+
twin_coord_t top,
55+
twin_coord_t right,
56+
twin_argb32_t *pixels,
57+
void *closure)
58+
{
59+
twin_drm_t *tx = PRIV(closure);
60+
61+
if (tx->fb_base == MAP_FAILED)
62+
return;
63+
64+
twin_coord_t width = right - left;
65+
int line_length = 4 * tx->width;
66+
off_t off = line_length * top + 4 * left;
67+
unsigned char *dest = (unsigned char *) tx->fb_base + off;
68+
memcpy(dest, pixels, width * sizeof(uint32_t));
69+
}
70+
71+
static void twin_drm_get_screen_size(twin_drm_t *tx, int *width, int *height)
72+
{
73+
*width = CONN(tx)->modes[0].hdisplay;
74+
*height = CONN(tx)->modes[0].vdisplay;
75+
}
76+
77+
static bool twin_drm_work(void *closure)
78+
{
79+
twin_screen_t *screen = SCREEN(closure);
80+
81+
if (twin_screen_damaged(screen))
82+
twin_screen_update(screen);
83+
84+
return true;
85+
}
86+
87+
static bool get_resources(int fd, drmModeResPtr *resources)
88+
{
89+
*resources = drmModeGetResources(fd);
90+
if (*resources == NULL) {
91+
log_error("drmModeGetResources failed");
92+
return false;
93+
}
94+
return true;
95+
}
96+
97+
static drmModeConnectorPtr find_drm_connector(int fd, drmModeResPtr resources)
98+
{
99+
drmModeConnectorPtr connector_ptr = NULL;
100+
101+
for (int i = 0; i < resources->count_connectors; i++) {
102+
connector_ptr = drmModeGetConnector(fd, resources->connectors[i]);
103+
if (connector_ptr && connector_ptr->connection == DRM_MODE_CONNECTED) {
104+
/* it's connected, let's use this! */
105+
break;
106+
}
107+
drmModeFreeConnector(connector_ptr);
108+
connector_ptr = NULL;
109+
}
110+
111+
return connector_ptr;
112+
}
113+
114+
static bool create_fb(twin_drm_t *tx)
115+
{
116+
/* Create dumb buffer */
117+
struct drm_mode_create_dumb create_req = {
118+
.width = tx->width,
119+
.height = tx->height,
120+
.bpp = 32,
121+
};
122+
if (ioctl(tx->drm_dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
123+
log_error("Cannot create dumb buffer");
124+
return false;
125+
}
126+
tx->fb_len = create_req.size;
127+
128+
/* Create framebuffer object for the dumb-buffer */
129+
if (drmModeAddFB(tx->drm_dri_fd, tx->width, tx->height, 24, 32,
130+
create_req.pitch, create_req.handle, &tx->fb_id) != 0) {
131+
log_error("Cannot create framebubber");
132+
munmap(tx->fb_base, tx->fb_len);
133+
return false;
134+
}
135+
136+
/* Prepare buffer for memory mapping */
137+
struct drm_mode_map_dumb map_req = {
138+
.handle = create_req.handle,
139+
};
140+
if (ioctl(tx->drm_dri_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req) < 0) {
141+
log_error("Cannot map dumb buffer");
142+
return false;
143+
}
144+
145+
/* Perform actual memory mapping */
146+
tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED,
147+
tx->drm_dri_fd, map_req.offset);
148+
if (tx->fb_base == MAP_FAILED) {
149+
log_error("Failed to mmap framebuffer");
150+
return false;
151+
}
152+
153+
return true;
154+
}
155+
156+
static bool twin_drm_apply_config(twin_drm_t *tx)
157+
{
158+
/* Retrieve resources */
159+
if (!get_resources(tx->drm_dri_fd, &RES(tx)))
160+
return false;
161+
162+
/* Find a connected connector */
163+
CONN(tx) = find_drm_connector(tx->drm_dri_fd, RES(tx));
164+
if (!CONN(tx))
165+
goto bail_conn;
166+
167+
/* Get the mode information */
168+
drmModeModeInfo mode = CONN(tx)->modes[0];
169+
tx->width = mode.hdisplay;
170+
tx->height = mode.vdisplay;
171+
172+
/* Create the framebuffer */
173+
if (!create_fb(tx))
174+
goto bail_fb;
175+
176+
/* Set the mode on a CRTC */
177+
CRTC(tx) = drmModeGetCrtc(tx->drm_dri_fd, RES(tx)->crtcs[0]);
178+
if (drmModeSetCrtc(tx->drm_dri_fd, CRTC(tx)->crtc_id, tx->fb_id, 0, 0,
179+
&CONN(tx)->connector_id, 1, &mode) != 0) {
180+
log_error("drmModeSetCrtc failed");
181+
goto bail_crtc;
182+
}
183+
184+
return true;
185+
186+
bail_crtc:
187+
drmModeRmFB(tx->drm_dri_fd, tx->fb_id);
188+
munmap(tx->fb_base, tx->fb_len);
189+
bail_fb:
190+
drmModeFreeCrtc(CRTC(tx));
191+
drmModeFreeConnector(CONN(tx));
192+
bail_conn:
193+
drmModeFreeResources(RES(tx));
194+
return false;
195+
}
196+
197+
static int twin_vt_open(int vt_num)
198+
{
199+
int fd;
200+
201+
char vt_dev[30] = {0};
202+
snprintf(vt_dev, 30, "/dev/tty%d", vt_num);
203+
204+
fd = open(vt_dev, O_RDWR);
205+
if (fd < 0) {
206+
log_error("Failed to open %s", vt_dev);
207+
}
208+
209+
return fd;
210+
}
211+
212+
/* Virtual terminal part is same as fbdev */
213+
static bool twin_vt_setup(twin_drm_t *tx)
214+
{
215+
/* Open VT0 to inquire information */
216+
if ((tx->vt_fd = twin_vt_open(0)) < -1) {
217+
log_error("Failed to open VT0");
218+
return false;
219+
}
220+
221+
/* Inquire for current VT number */
222+
struct vt_stat vt;
223+
if (ioctl(tx->vt_fd, VT_GETSTATE, &vt) == -1) {
224+
log_error("Failed to get VT number");
225+
return false;
226+
}
227+
tx->vt_num = vt.v_active;
228+
229+
/* Open the VT */
230+
if ((tx->vt_fd = twin_vt_open(tx->vt_num)) < -1) {
231+
return false;
232+
}
233+
234+
/* Set VT to graphics mode to inhibit command-line text */
235+
if (ioctl(tx->vt_fd, KDSETMODE, KD_GRAPHICS) < 0) {
236+
log_error("Failed to set KD_GRAPHICS mode");
237+
return false;
238+
}
239+
240+
return true;
241+
}
242+
243+
twin_context_t *twin_drm_init(int width, int height)
244+
{
245+
/* Get environment variable to execute */
246+
char *drm_dri_path = getenv(DRM_DRI_NAME);
247+
if (!drm_dri_path) {
248+
log_info("Environment variable $DRI_CARD not set, use %s by default",
249+
DRM_DRI_DEFAULT);
250+
drm_dri_path = DRM_DRI_DEFAULT;
251+
}
252+
printf("%s\n", drm_dri_path);
253+
254+
twin_context_t *ctx = calloc(1, sizeof(twin_context_t));
255+
if (!ctx)
256+
return NULL;
257+
258+
ctx->priv = calloc(1, sizeof(twin_drm_t));
259+
if (!ctx->priv)
260+
return NULL;
261+
262+
twin_drm_t *tx = ctx->priv;
263+
264+
/* Open the DRM driver */
265+
tx->drm_dri_fd = open(drm_dri_path, O_RDWR);
266+
if (tx->drm_dri_fd < 0) {
267+
log_error("Failed to open %s", drm_dri_path);
268+
goto bail;
269+
}
270+
271+
/* Set up virtual terminal environment */
272+
if (!twin_vt_setup(tx)) {
273+
goto bail_dri_card_fd;
274+
}
275+
276+
/* Apply configurations to the DRM driver*/
277+
if (!twin_drm_apply_config(tx)) {
278+
log_error("Failed to apply configurations to the DRM driver");
279+
goto bail_vt_fd;
280+
}
281+
282+
/* Create TWIN screen */
283+
ctx->screen =
284+
twin_screen_create(width, height, NULL, _twin_drm_put_span, ctx);
285+
286+
/* Create Linux input system object */
287+
tx->input = twin_linux_input_create(ctx->screen);
288+
if (!tx->input) {
289+
log_error("Failed to create Linux input system object");
290+
goto bail_screen;
291+
}
292+
293+
/* Setup file handler and work functions */
294+
twin_set_work(twin_drm_work, TWIN_WORK_REDISPLAY, ctx);
295+
296+
return ctx;
297+
298+
bail_screen:
299+
twin_screen_destroy(ctx->screen);
300+
bail_vt_fd:
301+
close(tx->vt_fd);
302+
bail_dri_card_fd:
303+
close(tx->drm_dri_fd);
304+
bail:
305+
free(ctx->priv);
306+
free(ctx);
307+
return NULL;
308+
}
309+
310+
311+
static void twin_drm_configure(twin_context_t *ctx)
312+
{
313+
int width, height;
314+
twin_drm_t *tx = PRIV(ctx);
315+
twin_drm_get_screen_size(tx, &width, &height);
316+
twin_screen_resize(SCREEN(ctx), width, height);
317+
}
318+
319+
static void twin_drm_exit(twin_context_t *ctx)
320+
{
321+
if (!ctx)
322+
return;
323+
324+
twin_drm_t *tx = PRIV(ctx);
325+
ioctl(tx->vt_fd, KDSETMODE, KD_TEXT);
326+
munmap(tx->fb_base, tx->fb_len);
327+
drmModeRmFB(tx->drm_dri_fd, tx->fb_id);
328+
drmModeFreeCrtc(CRTC(tx));
329+
drmModeFreeConnector(CONN(tx));
330+
drmModeFreeResources(RES(tx));
331+
twin_linux_input_destroy(tx->input);
332+
close(tx->vt_fd);
333+
close(tx->drm_dri_fd);
334+
free(ctx->priv);
335+
free(ctx);
336+
}
337+
338+
339+
/* Register the Linux framebuffer backend */
340+
341+
const twin_backend_t g_twin_backend = {
342+
.init = twin_drm_init,
343+
.configure = twin_drm_configure,
344+
.exit = twin_drm_exit,
345+
};

configs/Kconfig

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ config BACKEND_FBDEV
1212
bool "Linux framebuffer support"
1313
select CURSOR
1414

15+
config BACKEND_DRM
16+
bool "DRM support"
17+
select CURSOR
18+
1519
config BACKEND_SDL
1620
bool "SDL video output support"
1721

0 commit comments

Comments
 (0)