diff --git a/Makefile b/Makefile index a0028ab..4fd1298 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,16 @@ ifeq ($(CONFIG_BACKEND_FBDEV), y) BACKEND = fbdev libtwin.a_files-y += backend/fbdev.c libtwin.a_files-y += backend/linux_input.c +libtwin.a_files-y += backend/linux_vt.c +endif + +ifeq ($(CONFIG_BACKEND_DRM), y) +BACKEND = drm +libtwin.a_files-y += backend/drm.c +libtwin.a_files-y += backend/linux_input.c +libtwin.a_files-y += backend/linux_vt.c +libtwin.a_cflags-y += $(shell pkg-config --cflags libdrm) +TARGET_LIBS += $(shell pkg-config --libs libdrm) endif ifeq ($(CONFIG_BACKEND_VNC), y) diff --git a/backend/drm.c b/backend/drm.c new file mode 100644 index 0000000..d423b9c --- /dev/null +++ b/backend/drm.c @@ -0,0 +1,337 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University, Taiwan + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux_input.h" +#include "linux_vt.h" +#include "twin_backend.h" + +#define DRM_DRI_NAME "DRI_CARD" +#define DRM_DRI_DEFAULT "/dev/dri/card0" +#define SCREEN(x) ((twin_context_t *) x)->screen +#define RES(x) ((twin_drm_t *) x)->res +#define CONN(x) ((twin_drm_t *) x)->conn +#define CRTC(x) ((twin_drm_t *) x)->crtc +#define PRIV(x) ((twin_drm_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; + + /* DRM driver */ + int width, height; + int drm_dri_fd; + drmModeResPtr res; + drmModeConnectorPtr conn; + drmModeCrtcPtr crtc; + uint32_t fb_id; + uint32_t *fb_base; + size_t fb_len; + // int display_width; +} twin_drm_t; + +static void _twin_drm_put_span(twin_coord_t left, + twin_coord_t top, + twin_coord_t right, + twin_argb32_t *pixels, + void *closure) +{ + twin_drm_t *tx = PRIV(closure); + + if (tx->fb_base == MAP_FAILED) + return; + + twin_coord_t width = right - left; + int line_length = 4 * tx->width; + off_t off = line_length * top + 4 * left; + unsigned char *dest = (unsigned char *) tx->fb_base + off; + memcpy(dest, pixels, width * sizeof(uint32_t)); +} + +static void twin_drm_get_screen_size(twin_drm_t *tx, int *width, int *height) +{ + *width = CONN(tx)->modes[0].hdisplay; + *height = CONN(tx)->modes[0].vdisplay; +} + +static bool twin_drm_work(void *closure) +{ + twin_screen_t *screen = SCREEN(closure); + + if (twin_screen_damaged(screen)) + twin_screen_update(screen); + + return true; +} + +static bool get_resources(int fd, drmModeResPtr *resources) +{ + *resources = drmModeGetResources(fd); + if (*resources == NULL) { + log_error("drmModeGetResources failed"); + return false; + } + return true; +} + +static drmModeConnectorPtr find_drm_connector(int fd, drmModeResPtr resources) +{ + drmModeConnectorPtr connector_ptr = NULL; + + for (int i = 0; i < resources->count_connectors; i++) { + connector_ptr = drmModeGetConnector(fd, resources->connectors[i]); + if (connector_ptr && connector_ptr->connection == DRM_MODE_CONNECTED) { + /* it's connected, let's use this! */ + break; + } + drmModeFreeConnector(connector_ptr); + connector_ptr = NULL; + } + + return connector_ptr; +} + +static bool create_fb(twin_drm_t *tx) +{ + /* Create dumb buffer */ + struct drm_mode_create_dumb create_req = { + .width = tx->width, + .height = tx->height, + .bpp = 32, + }; + if (ioctl(tx->drm_dri_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) { + log_error("Cannot create dumb buffer"); + return false; + } + tx->fb_len = create_req.size; + + /* Create framebuffer object for the dumb-buffer */ + if (drmModeAddFB(tx->drm_dri_fd, tx->width, tx->height, 24, 32, + create_req.pitch, create_req.handle, &tx->fb_id) != 0) { + log_error("Cannot create framebubber"); + return false; + } + + /* Prepare buffer for memory mapping */ + struct drm_mode_map_dumb map_req = { + .handle = create_req.handle, + }; + if (ioctl(tx->drm_dri_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req) < 0) { + log_error("Cannot map dumb buffer"); + goto bail_fb; + } + + /* Perform actual memory mapping */ + tx->fb_base = mmap(NULL, tx->fb_len, PROT_READ | PROT_WRITE, MAP_SHARED, + tx->drm_dri_fd, map_req.offset); + if (tx->fb_base == MAP_FAILED) { + log_error("Failed to mmap framebuffer"); + goto bail_fb; + } + + return true; + +bail_fb: + drmModeRmFB(tx->drm_dri_fd, tx->fb_id); + return false; +} + +static bool twin_drm_apply_config(twin_drm_t *tx) +{ + /* Retrieve resources */ + if (!get_resources(tx->drm_dri_fd, &RES(tx))) + return false; + + /* Find a connected connector */ + CONN(tx) = find_drm_connector(tx->drm_dri_fd, RES(tx)); + if (!CONN(tx)) + goto bail_res; + + /* Get the mode information */ + drmModeModeInfo mode = CONN(tx)->modes[0]; + tx->width = mode.hdisplay; + tx->height = mode.vdisplay; + + /* Create the framebuffer */ + if (!create_fb(tx)) + goto bail_conn; + + /* Set the mode on a CRTC */ + CRTC(tx) = drmModeGetCrtc(tx->drm_dri_fd, RES(tx)->crtcs[0]); + if (!CRTC(tx)) + goto bail_mmap; + + if (drmModeSetCrtc(tx->drm_dri_fd, CRTC(tx)->crtc_id, tx->fb_id, 0, 0, + &CONN(tx)->connector_id, 1, &mode) != 0) { + log_error("drmModeSetCrtc failed"); + goto bail_crtc; + } + + return true; + +bail_crtc: + drmModeFreeCrtc(CRTC(tx)); +bail_mmap: + munmap(tx->fb_base, tx->fb_len); + drmModeRmFB(tx->drm_dri_fd, tx->fb_id); +bail_conn: + drmModeFreeConnector(CONN(tx)); +bail_res: + drmModeFreeResources(RES(tx)); + return false; +} + +/* Virtual terminal part is same as fbdev */ +static bool twin_vt_setup(twin_drm_t *tx) +{ + /* Open VT0 to inquire information */ + if ((tx->vt_fd = twin_vt_open(0)) < -1) { + log_error("Failed to open VT0"); + 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_drm_init(int width, int height) +{ + /* Get environment variable to execute */ + char *drm_dri_path = getenv(DRM_DRI_NAME); + if (!drm_dri_path) { + log_info("Environment variable $DRI_CARD not set, use %s by default", + DRM_DRI_DEFAULT); + drm_dri_path = DRM_DRI_DEFAULT; + } + + twin_context_t *ctx = calloc(1, sizeof(twin_context_t)); + if (!ctx) + return NULL; + + ctx->priv = calloc(1, sizeof(twin_drm_t)); + if (!ctx->priv) + return NULL; + + twin_drm_t *tx = ctx->priv; + + /* Open the DRM driver */ + tx->drm_dri_fd = open(drm_dri_path, O_RDWR); + if (tx->drm_dri_fd < 0) { + log_error("Failed to open %s", drm_dri_path); + goto bail; + } + + /* Set up virtual terminal environment */ + if (!twin_vt_setup(tx)) { + goto bail_dri_card_fd; + } + + /* Apply configurations to the DRM driver*/ + if (!twin_drm_apply_config(tx)) { + log_error("Failed to apply configurations to the DRM driver"); + goto bail_vt_fd; + } + + /* Create TWIN screen */ + ctx->screen = + twin_screen_create(width, height, NULL, _twin_drm_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_screen; + } + + /* Setup file handler and work functions */ + twin_set_work(twin_drm_work, TWIN_WORK_REDISPLAY, ctx); + + return ctx; + +bail_screen: + twin_screen_destroy(ctx->screen); +bail_vt_fd: + close(tx->vt_fd); +bail_dri_card_fd: + close(tx->drm_dri_fd); +bail: + free(ctx->priv); + free(ctx); + return NULL; +} + + +static void twin_drm_configure(twin_context_t *ctx) +{ + int width, height; + twin_drm_t *tx = PRIV(ctx); + twin_drm_get_screen_size(tx, &width, &height); + twin_screen_resize(SCREEN(ctx), width, height); +} + +static void twin_drm_exit(twin_context_t *ctx) +{ + if (!ctx) + return; + + twin_drm_t *tx = PRIV(ctx); + ioctl(tx->vt_fd, KDSETMODE, KD_TEXT); + munmap(tx->fb_base, tx->fb_len); + drmModeRmFB(tx->drm_dri_fd, tx->fb_id); + drmModeFreeCrtc(CRTC(tx)); + drmModeFreeConnector(CONN(tx)); + drmModeFreeResources(RES(tx)); + twin_linux_input_destroy(tx->input); + close(tx->vt_fd); + close(tx->drm_dri_fd); + free(ctx->priv); + free(ctx); +} + + +/* Register the Linux DRM backend */ + +const twin_backend_t g_twin_backend = { + .init = twin_drm_init, + .configure = twin_drm_configure, + .exit = twin_drm_exit, +}; diff --git a/backend/fbdev.c b/backend/fbdev.c index d7fb08e..cde8b67 100644 --- a/backend/fbdev.c +++ b/backend/fbdev.c @@ -15,6 +15,7 @@ #include #include "linux_input.h" +#include "linux_vt.h" #include "twin_backend.h" #include "twin_private.h" @@ -139,21 +140,6 @@ static bool twin_fbdev_apply_config(twin_fbdev_t *tx) 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 */ diff --git a/backend/linux_vt.c b/backend/linux_vt.c new file mode 100644 index 0000000..8a09f91 --- /dev/null +++ b/backend/linux_vt.c @@ -0,0 +1,24 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University, Taiwan + * All rights reserved. + */ + +#include "linux_vt.h" +#include +#include "twin_private.h" + +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; +} diff --git a/backend/linux_vt.h b/backend/linux_vt.h new file mode 100644 index 0000000..7e1fb57 --- /dev/null +++ b/backend/linux_vt.h @@ -0,0 +1,12 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2024 National Cheng Kung University, Taiwan + * All rights reserved. + */ + +#ifndef _LINUX_VT_H__ +#define _LINUX_VT_H__ + +int twin_vt_open(int vt_num); + +#endif diff --git a/configs/Kconfig b/configs/Kconfig index d85b63d..9c2f64e 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -12,6 +12,10 @@ config BACKEND_FBDEV bool "Linux framebuffer support" select CURSOR +config BACKEND_DRM + bool "DRM support" + select CURSOR + config BACKEND_SDL bool "SDL video output support"