aboutsummaryrefslogtreecommitdiff
path: root/src/display.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/display.c')
-rw-r--r--src/display.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/src/display.c b/src/display.c
new file mode 100644
index 0000000..69c7bb2
--- /dev/null
+++ b/src/display.c
@@ -0,0 +1,623 @@
+#include "string.h"
+#include "stdlib.h"
+#include "signal.h"
+#include "fcntl.h"
+#include "errno.h"
+#include "termios.h"
+#include "unistd.h"
+#include "poll.h"
+#include "dirent.h"
+#include "termio.h"
+#include "stdio.h"
+
+#include "libdrm/drm_mode.h"
+#include "sys/vt.h"
+#include "sys/kd.h"
+
+#include "sys/ioctl.h"
+#include "sys/mman.h"
+
+#include "display.h"
+#include "system.h"
+#include "logger.h"
+
+
+#define REL_SIGNAL SIGUSR1
+#define ACQ_SIGNAL SIGUSR2
+#define REL_EVENT 1
+#define ACQ_EVENT 2
+
+
+Display __display = {0};
+
+
+typedef struct {
+ struct drm_mode_card_res drmRes;
+ __u64 *drmRes_fbs;
+ __u32 *drmRes_conns;
+ __u64 *drmRes_encs;
+ __u64 *drmRes_crtcs;
+
+ struct drm_mode_get_connector connector;
+ __u64 connProps[ 16 ];
+ __u64 connPropVals[ 16 ];
+ __u64 connEncs[ 16 ];
+ struct drm_mode_modeinfo connModes[ 32 ];
+ struct drm_mode_modeinfo *mode;
+
+ struct drm_mode_create_dumb drmCreateDumb[2];
+ struct drm_mode_map_dumb drmMapDumb[2];
+ struct drm_mode_fb_cmd drmFBCmd[2];
+} DRMData_d;
+
+static DRMData_d drmData = {0,};
+
+static Size vtPipe[2] = { -1, -1 };
+
+static struct termios termOldConfig;
+
+
+static Size _get_drm_resources( Size fd );
+static Size _get_drm_connector( Size fd );
+static Size _get_drm_crtc( Size fd );
+static Size _get_drm_framebuffer( Size fd );
+
+static int _is_card( const struct dirent *f );
+static Size _open_graphics_device( Size *fd );
+
+static Size _init_vt_switch( void );
+static void _vt_release( void );
+static void _vt_acquire( void );
+static void _vt_switch_sighandler(int sig);
+static Size _has_signal(Size signo);
+static Size _set_signal(Size signo, void(*sig_handler)(int));
+
+
+Size display_init( void )
+{
+ Size i, fd;
+
+ struct termios termConfig;
+
+
+ if(!isatty(STDIN_FILENO)) {
+ logf( "stdin is not a terminal" );
+ return E33_EXIT_FAILURE;
+ }
+
+ if( tcgetattr(STDIN_FILENO, &termOldConfig) == -1 ) {
+ logf( "Could not get terminal attributes" );
+ return E33_EXIT_FAILURE;
+ }
+ else {
+ termConfig = termOldConfig;
+ termConfig.c_iflag &= ~(IGNBRK | BRKINT | PARMRK);
+ termConfig.c_lflag &= ~(ICANON | ECHO | IEXTEN | TOSTOP);
+ termConfig.c_lflag |= ISIG;
+ termConfig.c_cc[VTIME] = 0;
+ termConfig.c_cc[VMIN] = 0;
+ termConfig.c_cc[VSTART] = 0;
+ termConfig.c_cc[VSTOP] = 0;
+
+ if( tcsetattr(STDIN_FILENO, TCSANOW, &termConfig) == -1 ) {
+ logf( "Could not set terminal attributes" );
+ return E33_EXIT_FAILURE;
+ }
+ }
+
+ if( _init_vt_switch() ) {
+ logw( "Failed to initialize VT switcher. VT switching will not be available" );
+ }
+
+
+ if( _open_graphics_device( &fd ) ) {
+ logf( "Failed to find a suitable graphics device" );
+ return E33_EXIT_FAILURE;
+ }
+
+
+ if( e33_ioctl( fd, DRM_IOCTL_SET_MASTER, 0 ) == -1 )
+ {
+ logf( "Failed to set DRM master" );
+ return E33_EXIT_FAILURE;
+ }
+
+
+ if( _get_drm_resources( fd ) ) {
+ logf( "Failed to get resources" );
+ return E33_EXIT_FAILURE;
+ }
+ if( _get_drm_connector( fd ) ) {
+ logf( "Failed to set connector" );
+ return E33_EXIT_FAILURE;
+ }
+ if( _get_drm_framebuffer( fd ) ) {
+ logf( "Failed to set framebuffer" );
+ return E33_EXIT_FAILURE;
+ }
+ if( _get_drm_crtc( fd ) ) {
+ logf( "Failed to set crtc" );
+ return E33_EXIT_FAILURE;
+ }
+
+
+ __display.devFd = fd;
+ __display.surface.w = drmData.mode->hdisplay;
+ __display.surface.h = drmData.mode->vdisplay;
+ __display.fb.size = (Size)drmData.drmCreateDumb[0].size;
+ __display.surface.data = __display.fb.map[0];
+ __display.active = 1;
+
+
+ return E33_EXIT_SUCCESS;
+}
+
+void display_flip( void )
+{
+ static u8 i = 1;
+
+ struct drm_mode_crtc_page_flip flip = { 0, };
+
+ __display.surface.data = __display.fb.map[i];
+ i ^= 1;
+ __display.crtc.fb_id = __display.fb.id[i];
+
+ if( e33_ioctl( __display.devFd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) {
+ return;
+ }
+
+ flip.fb_id = __display.fb.id[i];
+ flip.crtc_id = __display.crtc.crtc_id;
+ flip.user_data = ((__u64)(&__display.crtc.crtc_id));
+ flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
+
+ if( e33_ioctl( __display.devFd, (int)DRM_IOCTL_MODE_PAGE_FLIP, &flip ) == -1 ) {
+ return;
+ }
+
+ /* TODO remove later */
+ memset( __display.surface.data, 0, (uSize)__display.fb.size );
+}
+
+void display_term( void )
+{
+ if( e33_ioctl( __display.devFd, DRM_IOCTL_DROP_MASTER, 0 ) == -1 ) {
+ logw( "Failed to drop drm master. Oh well." );
+ }
+
+ if( ioctl(__display.ttyFd, VT_RELDISP, 1) == -1 ) {
+ logw( "Failed to drop release vt. Oh well." );
+ }
+
+ if( munmap( __display.fb.map[0], (uSize)__display.fb.size ) == -1 ) {
+ logw( "Failed to unmap framebuffer[0]. Oh well." );
+ }
+ if( munmap( __display.fb.map[1], (uSize)__display.fb.size ) == -1 ) {
+ logw( "Failed to unmap framebuffer[1]. Oh well." );
+ }
+
+ free( drmData.drmRes_fbs );
+ free( drmData.drmRes_crtcs );
+ free( drmData.drmRes_encs );
+ free( drmData.drmRes_conns );
+
+ if( close( __display.ttyFd ) == -1 ) {
+ logw( "Failed to close TTY. Oh well." );
+ }
+ if( close( __display.devFd ) ) {
+ logw( "Failed to close graphics device. Oh well." );
+ }
+
+ if( tcsetattr(STDIN_FILENO, TCSAFLUSH, &termOldConfig) == -1 ) {
+ logw( "Failed to set stdin attributes. Oh well." );
+ }
+}
+
+
+void display_vtswitcher_poll( int timeout )
+{
+ struct pollfd fds[1];
+ unsigned char event;
+
+ fds[0].fd = vtPipe[0];
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+
+ poll( fds, 1, timeout );
+ if( !fds[0].revents ) {
+ return;
+ }
+
+ if( read(fds[0].fd, &event, sizeof(event)) != sizeof(event) ) {
+ logw( "Invalid VT switch events read");
+ return;
+ }
+
+ switch(event) {
+ case REL_EVENT: _vt_release();
+ break;
+ case ACQ_EVENT: _vt_acquire();
+ break;
+ }
+}
+
+static void _vt_release( void )
+{
+ logi( "VT release signal");
+
+ if( ioctl( __display.devFd, DRM_IOCTL_DROP_MASTER, 0 ) ) {
+ loge( "Could not drop DRM master" );
+ }
+
+ if( ioctl(__display.ttyFd, VT_RELDISP, 1) < 0 ) {
+ loge( "Failed to release VT");
+ }
+
+ __display.active = 0;
+}
+
+static void _vt_acquire( void )
+{
+ logi( "VT acquire signal");
+
+ if( ioctl( __display.devFd, DRM_IOCTL_SET_MASTER, 0 ) == -1 ) {
+ logw( "Could not become DRM master." );
+ }
+
+ if( ioctl(__display.ttyFd, VT_RELDISP, VT_ACKACQ) < 0 ) {
+ loge( "(vt_acquire) Failed to acquire VT.");
+ }
+
+ if( ioctl( __display.devFd, (int)DRM_IOCTL_MODE_SETCRTC, &__display.crtc ) == -1 ) {
+ loge( "Failed to set crtc on VT switch." );
+ }
+
+ __display.active = 1;
+}
+
+
+static int _set_pipe(int fd)
+{
+ if (fd >= 0)
+ {
+ int flags = fcntl(fd, F_GETFD);
+
+ if (flags == -1) {
+ logw( "Could not get flags for VT switcher pipe");
+ return E33_EXIT_FAILURE;
+ }
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
+ logw( "Could not set flags for VT switcher pipe");
+ return E33_EXIT_FAILURE;
+ }
+ }
+
+ return E33_EXIT_SUCCESS;
+}
+
+static Size _has_signal(Size signo)
+{
+ struct sigaction sact = {0};
+ sigaction(signo, 0, &sact);
+ return sact.sa_handler != 0;
+}
+
+static Size _set_signal(Size signo, void(*sig_handler)(int))
+{
+ struct sigaction sact = {0};
+ sact.sa_handler = sig_handler;
+ sigemptyset(&sact.sa_mask);
+ sact.sa_flags = SA_RESTART;
+ return sigaction(signo, &sact, NULL);
+}
+
+static void _vt_switch_sighandler(int sig)
+{
+ unsigned char event = REL_EVENT;
+
+ if( sig == ACQ_SIGNAL ) {
+ event = ACQ_EVENT;
+ }
+
+ (void)write( vtPipe[1], &event, sizeof(event) );
+}
+
+static Size _init_vt_switch( void )
+{
+ struct vt_mode vt_mode = {0,};
+ sigset_t set;
+
+ __display.ttyFd = -1;
+
+
+ /* Init VT pipes */
+ if (pipe(vtPipe) != 0) {
+ vtPipe[0] = vtPipe[1] = -1;
+ loge( "Could not set VT pipes");
+ return E33_EXIT_FAILURE;
+ }
+
+ if( _set_pipe(vtPipe[0]) ) {
+ loge( "Could not set VT read pipe" );
+ return E33_EXIT_FAILURE;
+ }
+ if( _set_pipe(vtPipe[1]) ) {
+ loge( "Could not set VT write pipe" );
+ return E33_EXIT_FAILURE;
+ }
+ /**/
+
+
+ if( __display.ttyFd = open("/dev/tty", O_RDWR | O_CLOEXEC ) < 0 ) {
+ loge( "Could not open TTY for VT control" );
+ return E33_EXIT_FAILURE;
+ }
+
+
+ /* Setup signals */
+ if( _has_signal(REL_SIGNAL) ) {
+ loge( "VT release signal is already in use");
+ return E33_EXIT_FAILURE;
+ }
+ if( _has_signal(ACQ_SIGNAL) ) {
+ loge( "VT acquire signal is already in use");
+ return E33_EXIT_FAILURE;
+ }
+
+ if( _set_signal( REL_SIGNAL, _vt_switch_sighandler ) ) {
+ loge( "Could not set relese signal handler");
+ return E33_EXIT_FAILURE;
+ }
+ if( _set_signal( ACQ_SIGNAL, _vt_switch_sighandler ) ) {
+ loge( "Could not set acquire signal handler");
+ return E33_EXIT_FAILURE;
+ }
+ /**/
+
+
+
+ if( ioctl(__display.ttyFd, VT_GETMODE, &vt_mode) < 0 ) {
+ logw( "Could not get VT mode" );
+ return E33_EXIT_FAILURE;
+ }
+
+ vt_mode.mode = VT_PROCESS;
+ vt_mode.relsig = REL_SIGNAL;
+ vt_mode.acqsig = ACQ_SIGNAL;
+ vt_mode.frsig = SIGIO;
+
+ if( ioctl(__display.ttyFd, VT_SETMODE, &vt_mode) < 0 ) {
+ logw( "Could not set VT mode" );
+ return E33_EXIT_FAILURE;
+ }
+
+ return E33_EXIT_SUCCESS;
+}
+
+
+static Size _get_drm_resources( Size fd )
+{
+ if( e33_ioctl(fd, (int)DRM_IOCTL_MODE_GETRESOURCES, &drmData.drmRes) == -1 ) {
+ logw( "Failed to init drm resources" );
+ return E33_EXIT_FAILURE;
+ }
+
+ drmData.drmRes_fbs = malloc( sizeof(__u64) * drmData.drmRes.count_fbs );
+ drmData.drmRes_crtcs = malloc( sizeof(__u64) * drmData.drmRes.count_crtcs );
+ drmData.drmRes_encs = malloc( sizeof(__u64) * drmData.drmRes.count_encoders );
+ drmData.drmRes_conns = malloc( sizeof(__u64) * drmData.drmRes.count_connectors );
+
+ /* DRM resource objects must also be zero'd before second call */
+ memset( drmData.drmRes_fbs, 0, sizeof(__u64) * drmData.drmRes.count_fbs );
+ memset( drmData.drmRes_crtcs, 0, sizeof(__u64) * drmData.drmRes.count_crtcs );
+ memset( drmData.drmRes_encs, 0, sizeof(__u64) * drmData.drmRes.count_encoders );
+ memset( drmData.drmRes_conns, 0, sizeof(__u64) * drmData.drmRes.count_connectors );
+
+ drmData.drmRes.fb_id_ptr = (__u64)drmData.drmRes_fbs;
+ drmData.drmRes.crtc_id_ptr = (__u64)drmData.drmRes_crtcs;
+ drmData.drmRes.connector_id_ptr = (__u64)drmData.drmRes_conns;
+ drmData.drmRes.encoder_id_ptr = (__u64)drmData.drmRes_encs;
+
+ /* Second call writes drm resource data using the given pointers */
+ if( ioctl( fd, (int)DRM_IOCTL_MODE_GETRESOURCES, &drmData.drmRes ) == -1 ) {
+ logw( "Failed to get drm resources" );
+ return E33_EXIT_FAILURE;
+ }
+
+ return E33_EXIT_SUCCESS;
+}
+
+
+static Size _get_drm_connector( Size fd )
+{
+ struct drm_mode_get_connector *connector = &__display.connector;
+ u16 i, j;
+
+ for( i = 0; i < drmData.drmRes.count_connectors; ++i )
+ {
+ connector->connector_id = drmData.drmRes_conns[i];
+
+ if( connector->connector_id <= 0 ) {
+ continue;
+ }
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector ) == -1 ) {
+ continue;
+ }
+
+ if( connector->connection && connector->count_modes > 0 &&
+ connector->encoder_id && connector->count_encoders > 0 )
+ {
+ connector->modes_ptr = (__u64)drmData.connModes;
+ connector->props_ptr = (__u64)drmData.connProps;
+ connector->prop_values_ptr = (__u64)drmData.connPropVals;
+ connector->encoders_ptr = (__u64)drmData.connEncs;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_GETCONNECTOR, connector) == -1 ) {
+ continue;
+ }
+
+ goto success;
+ }
+ }
+
+ loge( "Failed to find suitable connector");
+ return E33_EXIT_FAILURE;
+
+success:
+ drmData.mode = &drmData.connModes[0];
+
+ logi( "Using mode '%s'", drmData.mode->name );
+
+ /* TODO there has to be a better way */
+ if( _strcomp( drmData.mode->name, "1366x768" ) ) {
+ drmData.mode->hdisplay += 10;
+ }
+ return E33_EXIT_SUCCESS;
+}
+
+static Size _get_drm_crtc( Size fd )
+{
+ struct drm_mode_get_encoder *encoder = &__display.encoder;
+ struct drm_mode_crtc *crtc = &__display.crtc;
+
+ encoder->encoder_id = __display.connector.encoder_id;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_GETENCODER, encoder ) == -1 ) {
+ loge( "Failed to get encoder" );
+ return E33_EXIT_FAILURE;
+ }
+
+ crtc->crtc_id = encoder->crtc_id;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_GETCRTC, crtc ) == -1 ) {
+ loge( "Failed to get CRTC" );
+ return E33_EXIT_FAILURE;
+ }
+
+ crtc->fb_id = drmData.drmFBCmd[0].fb_id;
+ crtc->set_connectors_ptr = (__u64)drmData.drmRes_conns;
+ crtc->count_connectors = 1;
+ crtc->mode = *drmData.mode;
+ crtc->mode_valid = 1;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_SETCRTC, crtc ) == -1 ) {
+ loge( "Could not set CRTC" );
+ return E33_EXIT_FAILURE;
+ }
+
+ return E33_EXIT_SUCCESS;
+}
+
+static Size _get_drm_framebuffer( Size fd )
+{
+ struct drm_mode_create_dumb *drmCreateDumb = drmData.drmCreateDumb;
+ struct drm_mode_map_dumb *drmMapDumb = drmData.drmMapDumb;
+ struct drm_mode_fb_cmd *drmFBCmd = drmData.drmFBCmd;
+ struct drm_mode_modeinfo *mode = drmData.mode;
+ u8 i;
+
+ for( i = 0; i < 2; ++i )
+ {
+ drmCreateDumb[i].width = mode->hdisplay;
+ drmCreateDumb[i].height = mode->vdisplay;
+ drmCreateDumb[i].bpp = 32;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_CREATE_DUMB,
+ drmCreateDumb + i ) == -1 )
+ {
+ loge( "Failed to create dumb buffer #%d", i );
+ return E33_EXIT_FAILURE;
+ }
+
+ drmFBCmd[i].width = drmCreateDumb[i].width;
+ drmFBCmd[i].height = drmCreateDumb[i].height;
+ drmFBCmd[i].bpp = drmCreateDumb[i].bpp;
+ drmFBCmd[i].pitch = drmCreateDumb[i].pitch;
+ drmFBCmd[i].depth = 24;
+ drmFBCmd[i].handle = drmCreateDumb[i].handle;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_ADDFB, drmFBCmd + i ) == -1 ) {
+ loge( "Failed to add framebuffer #%d", i );
+ return E33_EXIT_FAILURE;
+ }
+
+ __display.fb.id[i] = drmFBCmd[i].fb_id;
+
+ drmMapDumb[i].handle = drmCreateDumb[i].handle;
+
+ if( e33_ioctl( fd, (int)DRM_IOCTL_MODE_MAP_DUMB, drmMapDumb + i ) == -1 )
+ {
+ loge( "Failed to map dumb buffer #%d", i );
+ return E33_EXIT_FAILURE;
+ }
+
+ __display.fb.map[i] =
+ mmap( 0, drmCreateDumb[i].size, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, drmMapDumb[i].offset );
+
+ if( __display.fb.map[i] == MAP_FAILED ) {
+ loge( "Failed to map framebuffer #%d", i );
+ return E33_EXIT_FAILURE;
+ }
+ }
+
+ return E33_EXIT_SUCCESS;
+}
+
+
+static int _is_card( const struct dirent *f )
+{
+ return _strcomp(f->d_name, "card");
+}
+
+
+static Size _open_graphics_device( Size *fd )
+{
+ Size i, num, _fd;
+ struct dirent **evfs;
+ char path[512];
+
+ num = scandir( "/dev/dri", &evfs, _is_card, 0 );
+
+ if( num <= 0 ) {
+ loge( "No graphics devices found" );
+ goto failure;
+ }
+
+ for( i = 0; i < num; ++i )
+ {
+ struct drm_get_cap cap = {0,};
+
+ snprintf( path, 512, "%s%s", "/dev/dri/", evfs[i]->d_name );
+
+ logi( "Trying '%s'", path );
+
+ if( (_fd = open(path, O_RDWR )) == -1 ) {
+ logw( "Failed to open graphics device '%s'", path );
+ continue;
+ }
+
+ cap.capability = DRM_CAP_DUMB_BUFFER;
+ if( e33_ioctl( _fd, DRM_IOCTL_GET_CAP, &cap ) == -1 ) {
+ logw( "Failed to get device capabilities for '%s'", path );
+ continue;
+ }
+
+ if( cap.value )
+ {
+ logi("Using device: %s", path);
+ *fd = _fd;
+ free( evfs );
+ return E33_EXIT_SUCCESS;
+ }
+
+ close( _fd );
+ }
+
+failure:
+ free( evfs );
+ return E33_EXIT_FAILURE;
+}
+