diff --git a/README.md b/README.md index 6d1d75e..1f1b8a9 100644 --- a/README.md +++ b/README.md @@ -98,12 +98,16 @@ You may override the defaults. In most cases, though, you won't need to do so: TSLIB_PLUGINDIR Plugin directory. Default: ${datadir}/plugins - TSLIB_CONSOLEDEVICE Console device. (not needed when using --with-sdl2) + TSLIB_CONSOLEDEVICE Console device. (not needed when using --with-sdl2 + or --with-drm) Default: /dev/tty - TSLIB_FBDEVICE Framebuffer device. + TSLIB_FBDEVICE Framebuffer device. (not needed when using --with-drm) Default: /dev/fb0 + TSLIB_DRMDEVICE DRM device. (only when built with --with-drm) + Default: /dev/dri/card0 + ### use the filtered result in your system (X.org method) If you're using X.org graphical X server, things should be very easy. Install tslib and [xf86-input-tslib](https://github.com/merge/xf86-input-tslib), @@ -648,6 +652,7 @@ compatibility. * libc (with libdl only when building dynamically linked) * libsdl2-dev (only when using `--with-sdl2` for [SDL2](https://www.libsdl.org/) graphical applications) +* libdrm-dev (only when using `--with-drm` for DRM/KMS graphical applications) ### related libraries @@ -773,6 +778,15 @@ implementation of the necessary graphical tools using SDL2. They are more portab but require more resources to run. To use them, make sure you have SDL2 and the development headers installed and use `./configure --with-sdl2`. +### DRM/KMS support for graphical tools + +On systems without a framebuffer device (`/dev/fb0`), graphical tools like +`ts_calibrate`, `ts_test`, and `ts_test_mt` can be built to use DRM/KMS +instead. This uses libdrm with dumb buffers for direct rendering. To use this, +make sure you have libdrm and the development headers installed and use +`./configure --with-drm`. The DRM device can be configured via the +`TSLIB_DRMDEVICE` environment variable (default: `/dev/dri/card0`). + ### portability tslib is cross-platform; you should be able to build it on a large variety of diff --git a/configure.ac b/configure.ac index df5c916..20c0422 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,17 @@ AS_IF([test "x$with_sdl2" = "xyes"], [ AC_CHECK_LIB([SDL2], [SDL_Init], [], [exit 1]) ]) +AC_ARG_WITH(drm, + [AS_HELP_STRING([--with-drm], [build graphical tools using DRM/KMS instead of fbdev])], + [with_drm=$withval], + [with_drm=no]) + +AM_CONDITIONAL(DRM, test x"$with_drm" = "xyes") +AS_IF([test "x$with_drm" = "xyes"], [ + AC_DEFINE(HAVE_LIBDRM, 1, [Use libdrm for graphical tools.]) + PKG_CHECK_MODULES([LIBDRM], [libdrm], [], [AC_MSG_ERROR([libdrm not found])]) +]) + # libts Library versioning # increment if the interface changed LT_CURRENT=10 diff --git a/doc/ts.conf.5 b/doc/ts.conf.5 index 3b06783..acb5652 100644 --- a/doc/ts.conf.5 +++ b/doc/ts.conf.5 @@ -65,6 +65,7 @@ We try to open /dev/input/ts, /dev/input/touchscreen and /dev/touchscreen/ucb1x0 \fBTSLIB_CONSOLEDEVICE\fR .RS 4 Tslib default console device\&. +Not needed when using \-\-with\-sdl2 or \-\-with\-drm\&. .sp Default: /dev/tty @@ -92,11 +93,21 @@ Default; \fBTSLIB_FBDEVICE\fR .RS 4 Framebuffer device to use for the touchscreen support\&. +Not used when built with \-\-with\-drm\&. .sp Default: /dev/fb0\&. .RE .PP +\fBTSLIB_DRMDEVICE\fR +.RS 4 +DRM device to use for the touchscreen support\&. +Only used when built with \-\-with\-drm\&. +.sp +Default: +/dev/dri/card0\&. +.RE +.PP \fBTSLIB_PLUGINDIR\fR .RS 4 Plugin directory for tslib\&. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4ee1d00..6bcbaf4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,11 +10,17 @@ option(with-sdl "build graphical tools like ts_calibrate using SDL" OFF) +option(with-drm "build graphical tools using DRM/KMS instead of fbdev" OFF) if (${with-sdl}) find_package(SDL2 REQUIRED) endif() +if (${with-drm}) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBDRM REQUIRED libdrm) +endif() + function(TSLIB_ADD_TEST_ON_PLATFORMS test_name) foreach(condition IN LISTS ARGN) if (${condition}) @@ -23,6 +29,10 @@ function(TSLIB_ADD_TEST_ON_PLATFORMS test_name) if (${with-sdl}) target_link_libraries(${test_name} PUBLIC SDL2) endif() + if (${with-drm}) + target_include_directories(${test_name} PRIVATE ${LIBDRM_INCLUDE_DIRS}) + target_link_libraries(${test_name} PUBLIC ${LIBDRM_LIBRARIES}) + endif() set(tslib_tests ${tslib_tests} ${test_name} PARENT_SCOPE) break() endif() @@ -30,8 +40,12 @@ function(TSLIB_ADD_TEST_ON_PLATFORMS test_name) endfunction(TSLIB_ADD_TEST_ON_PLATFORMS) -set(fbutils $<$:fbutils-bsd.c> - $<$:fbutils-linux.c>) +if (${with-drm}) + set(fbutils fbutils-drm.c) +else() + set(fbutils $<$:fbutils-bsd.c> + $<$:fbutils-linux.c>) +endif() if(${with-sdl}) diff --git a/tests/Makefile.am b/tests/Makefile.am index 60b7d1f..f1ea411 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -47,11 +47,17 @@ endif ts_test_SOURCES = ts_test.c testutils.c testutils.h fbutils.h font_8x8.c font_8x16.c font.h ts_test_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) +if DRM +ts_test_SOURCES += fbutils-drm.c +ts_test_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) +ts_test_LDADD += $(LIBDRM_LIBS) +else if FREEBSD ts_test_SOURCES += fbutils-bsd.c else ts_test_SOURCES += fbutils-linux.c endif +endif if SDL ts_test_mt_SOURCES = ts_test_mt_sdl.c sdlutils.c sdlutils.h @@ -59,12 +65,18 @@ ts_test_mt_LDADD = $(top_builddir)/src/libts.la -lSDL2 $(LIBEVDEV_LIBS) else ts_test_mt_SOURCES = ts_test_mt.c testutils.c testutils.h fbutils.h font_8x8.c font_8x16.c font.h ts_test_mt_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) +if DRM +ts_test_mt_SOURCES += fbutils-drm.c +ts_test_mt_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) +ts_test_mt_LDADD += $(LIBDRM_LIBS) +else if FREEBSD ts_test_mt_SOURCES += fbutils-bsd.c else ts_test_mt_SOURCES += fbutils-linux.c endif endif +endif ts_print_SOURCES = ts_print.c ts_print_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) @@ -84,20 +96,32 @@ ts_calibrate_LDADD = $(top_builddir)/src/libts.la -lSDL2 $(LIBEVDEV_LIBS) else ts_calibrate_SOURCES = ts_calibrate.c ts_calibrate.h ts_calibrate_common.c fbutils.h testutils.c testutils.h font_8x8.c font_8x16.c font.h ts_calibrate_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) +if DRM +ts_calibrate_SOURCES += fbutils-drm.c +ts_calibrate_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) +ts_calibrate_LDADD += $(LIBDRM_LIBS) +else if FREEBSD ts_calibrate_SOURCES += fbutils-bsd.c else ts_calibrate_SOURCES += fbutils-linux.c endif endif +endif ts_harvest_SOURCES = ts_harvest.c fbutils.h testutils.c testutils.h font_8x8.c font_8x16.c font.h ts_harvest_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) +if DRM +ts_harvest_SOURCES += fbutils-drm.c +ts_harvest_CFLAGS = $(AM_CFLAGS) $(LIBDRM_CFLAGS) +ts_harvest_LDADD += $(LIBDRM_LIBS) +else if FREEBSD ts_harvest_SOURCES += fbutils-bsd.c else ts_harvest_SOURCES += fbutils-linux.c endif +endif ts_finddev_SOURCES = ts_finddev.c ts_finddev_LDADD = $(top_builddir)/src/libts.la $(LIBEVDEV_LIBS) diff --git a/tests/fbutils-drm.c b/tests/fbutils-drm.c new file mode 100644 index 0000000..b64c09b --- /dev/null +++ b/tests/fbutils-drm.c @@ -0,0 +1,521 @@ +/* + * fbutils-drm.c + * + * Utility routines for DRM/KMS framebuffer interaction + * Alternative to fbutils-linux.c using libdrm instead of fbdev. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "font.h" +#include "fbutils.h" + +union multiptr { + uint8_t *p8; + uint16_t *p16; + uint32_t *p32; +}; + +static int drm_fd = -1; +static drmModeConnector *connector; +static drmModeEncoder *encoder; +static drmModeCrtc *orig_crtc; +static drmModeModeInfo mode; + +static uint32_t crtc_id; +static uint32_t connector_id; +static uint32_t fb_id; + +static uint32_t drm_handle; +static uint32_t drm_pitch; +static uint32_t drm_size; +static unsigned char *fbuffer; +static unsigned char **line_addr; + +static int32_t bytes_per_pixel; +static uint32_t colormap[256]; + +uint32_t xres, yres; +uint32_t xres_orig, yres_orig; +int8_t rotation; +int8_t alternative_cross; + +static char *defaultdrmdevice = "/dev/dri/card0"; + +static drmModeConnector *find_connector(int fd, drmModeRes *res) +{ + int i; + + for (i = 0; i < res->count_connectors; i++) { + drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[i]); + + if (conn && conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) + return conn; + + drmModeFreeConnector(conn); + } + + return NULL; +} + +static drmModeEncoder *find_encoder(int fd, drmModeConnector *conn) +{ + if (conn->encoder_id) + return drmModeGetEncoder(fd, conn->encoder_id); + + return NULL; +} + +static uint32_t find_crtc(int fd, drmModeRes *res, drmModeConnector *conn) +{ + drmModeEncoder *enc; + int i, j; + + /* Try the currently attached encoder */ + if (conn->encoder_id) { + enc = drmModeGetEncoder(fd, conn->encoder_id); + if (enc) { + if (enc->crtc_id) { + uint32_t id = enc->crtc_id; + + drmModeFreeEncoder(enc); + return id; + } + drmModeFreeEncoder(enc); + } + } + + /* No CRTC currently set - find a suitable default */ + for (i = 0; i < conn->count_encoders; i++) { + enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) + continue; + + for (j = 0; j < res->count_crtcs; j++) { + if (enc->possible_crtcs & (1u << j)) { + uint32_t id = res->crtcs[j]; + + drmModeFreeEncoder(enc); + return id; + } + } + drmModeFreeEncoder(enc); + } + + return 0; +} + +int open_framebuffer(void) +{ + char *drmdevice; + drmModeRes *res; + struct drm_mode_create_dumb creq; + struct drm_mode_map_dumb mreq; + uint32_t y, addr; + + if ((drmdevice = getenv("TSLIB_DRMDEVICE")) == NULL) + drmdevice = defaultdrmdevice; + + drm_fd = open(drmdevice, O_RDWR | O_CLOEXEC); + if (drm_fd < 0) { + perror("open drm device"); + return -1; + } + + res = drmModeGetResources(drm_fd); + if (!res) { + perror("drmModeGetResources"); + close(drm_fd); + drm_fd = -1; + return -1; + } + + connector = find_connector(drm_fd, res); + if (!connector) { + fprintf(stderr, "No connected DRM connector found\n"); + drmModeFreeResources(res); + close(drm_fd); + drm_fd = -1; + return -1; + } + connector_id = connector->connector_id; + + /* Use the first (preferred) mode */ + mode = connector->modes[0]; + + crtc_id = find_crtc(drm_fd, res, connector); + if (!crtc_id) { + fprintf(stderr, "No suitable CRTC found for DRM connector\n"); + drmModeFreeConnector(connector); + drmModeFreeResources(res); + close(drm_fd); + drm_fd = -1; + return -1; + } + + /* Save original CRTC state for restoration */ + orig_crtc = drmModeGetCrtc(drm_fd, crtc_id); + + encoder = find_encoder(drm_fd, connector); + + drmModeFreeResources(res); + + /* Create a dumb buffer */ + memset(&creq, 0, sizeof(creq)); + creq.width = mode.hdisplay; + creq.height = mode.vdisplay; + creq.bpp = 32; + + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) { + perror("DRM_IOCTL_MODE_CREATE_DUMB"); + goto err_cleanup; + } + + drm_handle = creq.handle; + drm_pitch = creq.pitch; + drm_size = creq.size; + bytes_per_pixel = 4; + + /* Create framebuffer object */ + if (drmModeAddFB(drm_fd, mode.hdisplay, mode.vdisplay, + 24, 32, drm_pitch, drm_handle, &fb_id)) { + perror("drmModeAddFB"); + goto err_destroy; + } + + /* Map the dumb buffer */ + memset(&mreq, 0, sizeof(mreq)); + mreq.handle = drm_handle; + + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq) < 0) { + perror("DRM_IOCTL_MODE_MAP_DUMB"); + goto err_rmfb; + } + + fbuffer = mmap(NULL, drm_size, PROT_READ | PROT_WRITE, + MAP_SHARED, drm_fd, mreq.offset); + if (fbuffer == MAP_FAILED) { + perror("mmap drm dumb buffer"); + goto err_rmfb; + } + memset(fbuffer, 0, drm_size); + + /* Set the mode on the CRTC */ + if (drmModeSetCrtc(drm_fd, crtc_id, fb_id, 0, 0, + &connector_id, 1, &mode)) { + perror("drmModeSetCrtc"); + goto err_unmap; + } + + xres_orig = mode.hdisplay; + yres_orig = mode.vdisplay; + + if (rotation & 1) { + xres = mode.vdisplay; + yres = mode.hdisplay; + } else { + xres = mode.hdisplay; + yres = mode.vdisplay; + } + + line_addr = malloc(sizeof(*line_addr) * mode.vdisplay); + if (!line_addr) + goto err_unmap; + + addr = 0; + for (y = 0; y < mode.vdisplay; y++, addr += drm_pitch) + line_addr[y] = fbuffer + addr; + + return 0; + +err_unmap: + munmap(fbuffer, drm_size); +err_rmfb: + drmModeRmFB(drm_fd, fb_id); +err_destroy: + { + struct drm_mode_destroy_dumb dreq = { .handle = drm_handle }; + drmIoctl(drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + } +err_cleanup: + if (encoder) + drmModeFreeEncoder(encoder); + drmModeFreeConnector(connector); + if (orig_crtc) + drmModeFreeCrtc(orig_crtc); + close(drm_fd); + drm_fd = -1; + return -1; +} + +void close_framebuffer(void) +{ + struct drm_mode_destroy_dumb dreq; + + if (drm_fd < 0) + return; + + memset(fbuffer, 0, drm_size); + + /* Restore original CRTC */ + if (orig_crtc) { + drmModeSetCrtc(drm_fd, orig_crtc->crtc_id, + orig_crtc->buffer_id, + orig_crtc->x, orig_crtc->y, + &connector_id, 1, &orig_crtc->mode); + drmModeFreeCrtc(orig_crtc); + } + + munmap(fbuffer, drm_size); + drmModeRmFB(drm_fd, fb_id); + + dreq.handle = drm_handle; + drmIoctl(drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); + + if (encoder) + drmModeFreeEncoder(encoder); + drmModeFreeConnector(connector); + close(drm_fd); + + free(line_addr); + + drm_fd = -1; + xres = 0; + yres = 0; + rotation = 0; +} + +void put_cross(int32_t x, int32_t y, uint32_t colidx) +{ + line(x - 10, y, x - 2, y, colidx); + line(x + 2, y, x + 10, y, colidx); + line(x, y - 10, x, y - 2, colidx); + line(x, y + 2, x, y + 10, colidx); + + if (!alternative_cross) { + line(x - 6, y - 9, x - 9, y - 9, colidx + 1); + line(x - 9, y - 8, x - 9, y - 6, colidx + 1); + line(x - 9, y + 6, x - 9, y + 9, colidx + 1); + line(x - 8, y + 9, x - 6, y + 9, colidx + 1); + line(x + 6, y + 9, x + 9, y + 9, colidx + 1); + line(x + 9, y + 8, x + 9, y + 6, colidx + 1); + line(x + 9, y - 6, x + 9, y - 9, colidx + 1); + line(x + 8, y - 9, x + 6, y - 9, colidx + 1); + } else if (alternative_cross == 1) { + line(x - 7, y - 7, x - 4, y - 4, colidx + 1); + line(x - 7, y + 7, x - 4, y + 4, colidx + 1); + line(x + 4, y - 4, x + 7, y - 7, colidx + 1); + line(x + 4, y + 4, x + 7, y + 7, colidx + 1); + } +} + +static void put_char(int32_t x, int32_t y, int32_t c, int32_t colidx) +{ + int32_t i, j, bits; + + for (i = 0; i < font_vga_8x8.height; i++) { + bits = font_vga_8x8.data[font_vga_8x8.height * c + i]; + for (j = 0; j < font_vga_8x8.width; j++, bits <<= 1) + if (bits & 0x80) + pixel(x + j, y + i, colidx); + } +} + +void put_string(int32_t x, int32_t y, char *s, uint32_t colidx) +{ + int32_t i; + + for (i = 0; *s; i++, x += font_vga_8x8.width, s++) + put_char(x, y, *s, colidx); +} + +void put_string_center(int32_t x, int32_t y, char *s, uint32_t colidx) +{ + size_t sl = strlen(s); + + put_string(x - (sl / 2) * font_vga_8x8.width, + y - font_vga_8x8.height / 2, s, colidx); +} + +void setcolor(uint32_t colidx, uint32_t value) +{ + uint32_t red, green, blue; + + if (colidx > 255) { +#ifdef DEBUG + fprintf(stderr, "WARNING: color index = %u, must be <256\n", + colidx); +#endif + return; + } + + /* DRM dumb buffer is always XRGB8888 (32bpp) */ + red = (value >> 16) & 0xff; + green = (value >> 8) & 0xff; + blue = value & 0xff; + colormap[colidx] = (red << 16) | (green << 8) | blue; +} + +static void __pixel_loc(int32_t x, int32_t y, union multiptr *loc) +{ + switch (rotation) { + case 0: + default: + loc->p8 = line_addr[y] + x * bytes_per_pixel; + break; + case 1: + loc->p8 = line_addr[x] + (yres - y - 1) * bytes_per_pixel; + break; + case 2: + loc->p8 = line_addr[yres - y - 1] + (xres - x - 1) * bytes_per_pixel; + break; + case 3: + loc->p8 = line_addr[xres - x - 1] + y * bytes_per_pixel; + break; + } +} + +static inline void __setpixel(union multiptr loc, uint32_t xormode, uint32_t color) +{ + /* DRM dumb buffer is always 32bpp */ + if (xormode) + *loc.p32 ^= color; + else + *loc.p32 = color; +} + +void pixel(int32_t x, int32_t y, uint32_t colidx) +{ + uint32_t xormode; + union multiptr loc; + + if ((x < 0) || ((uint32_t)x >= xres) || + (y < 0) || ((uint32_t)y >= yres)) + return; + + xormode = colidx & XORMODE; + colidx &= ~XORMODE; + + if (colidx > 255) { +#ifdef DEBUG + fprintf(stderr, "WARNING: color value = %u, must be <256\n", + colidx); +#endif + return; + } + + __pixel_loc(x, y, &loc); + __setpixel(loc, xormode, colormap[colidx]); +} + +void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t colidx) +{ + int32_t tmp; + int32_t dx = x2 - x1; + int32_t dy = y2 - y1; + + if (abs(dx) < abs(dy)) { + if (y1 > y2) { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + dx = -dx; dy = -dy; + } + x1 <<= 16; + /* dy is apriori >0 */ + dx = (dx << 16) / dy; + while (y1 <= y2) { + pixel(x1 >> 16, y1, colidx); + x1 += dx; + y1++; + } + } else { + if (x1 > x2) { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + dx = -dx; dy = -dy; + } + y1 <<= 16; + dy = dx ? (dy << 16) / dx : 0; + while (x1 <= x2) { + pixel(x1, y1 >> 16, colidx); + y1 += dy; + x1++; + } + } +} + +void rect(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t colidx) +{ + line(x1, y1, x2, y1, colidx); + line(x2, y1+1, x2, y2-1, colidx); + line(x2, y2, x1, y2, colidx); + line(x1, y2-1, x1, y1+1, colidx); +} + +void fillrect(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t colidx) +{ + int32_t tmp; + uint32_t xormode; + union multiptr loc; + + /* Clipping and sanity checking */ + if (x1 > x2) { tmp = x1; x1 = x2; x2 = tmp; } + if (y1 > y2) { tmp = y1; y1 = y2; y2 = tmp; } + + if (x1 < 0) + x1 = 0; + if ((uint32_t)x1 >= xres) + x1 = xres - 1; + + if (x2 < 0) + x2 = 0; + if ((uint32_t)x2 >= xres) + x2 = xres - 1; + + if (y1 < 0) + y1 = 0; + if ((uint32_t)y1 >= yres) + y1 = yres - 1; + + if (y2 < 0) + y2 = 0; + if ((uint32_t)y2 >= yres) + y2 = yres - 1; + + if ((x1 > x2) || (y1 > y2)) + return; + + xormode = colidx & XORMODE; + colidx &= ~XORMODE; + + if (colidx > 255) { +#ifdef DEBUG + fprintf(stderr, "WARNING: color value = %u, must be <256\n", + colidx); +#endif + return; + } + + colidx = colormap[colidx]; + + for (; y1 <= y2; y1++) { + for (tmp = x1; tmp <= x2; tmp++) { + __pixel_loc(tmp, y1, &loc); + __setpixel(loc, xormode, colidx); + } + } +}